瀏覽代碼

Support out-of-line definitions of members of generic classes and interfaces. (#4029)

Check the parameters specified in a name qualifier against the
parameters of the entity that the qualifier refers to.

For interfaces, this required adding minimal support for parameterized
interface names.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 年之前
父節點
當前提交
7792e5fce3

+ 168 - 147
toolchain/check/decl_name_stack.cpp

@@ -7,6 +7,8 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/check/merge.h"
+#include "toolchain/check/name_component.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/sem_ir/ids.h"
 
@@ -24,7 +26,6 @@ auto DeclNameStack::NameContext::prev_inst_id() -> SemIR::InstId {
              "that may change based on error handling).";
 
     case NameContext::State::Resolved:
-    case NameContext::State::ResolvedNonScope:
       return resolved_inst_id;
 
     case NameContext::State::Unresolved:
@@ -44,7 +45,7 @@ auto DeclNameStack::MakeEmptyNameContext() -> NameContext {
 auto DeclNameStack::MakeUnqualifiedName(SemIR::LocId loc_id,
                                         SemIR::NameId name_id) -> NameContext {
   NameContext context = MakeEmptyNameContext();
-  ApplyAndLookupName(context, loc_id, name_id, /*is_unqualified=*/true);
+  ApplyAndLookupName(context, loc_id, name_id);
   return context;
 }
 
@@ -59,8 +60,7 @@ auto DeclNameStack::FinishName(const NameComponent& name) -> NameContext {
   CARBON_CHECK(decl_name_stack_.back().state != NameContext::State::Finished)
       << "Finished name twice";
 
-  ApplyAndLookupName(decl_name_stack_.back(), name.name_loc_id, name.name_id,
-                     /*is_unqualified=*/false);
+  ApplyAndLookupName(decl_name_stack_.back(), name.name_loc_id, name.name_id);
 
   NameContext result = decl_name_stack_.back();
   decl_name_stack_.back().state = NameContext::State::Finished;
@@ -179,24 +179,43 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context,
   return SemIR::InstId::Invalid;
 }
 
-auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
-  if (name.implicit_params_id.is_valid() || name.params_id.is_valid()) {
-    context_->TODO(name.params_loc_id, "name qualifier with parameters");
-  }
+// Push a scope corresponding to a name qualifier. For example, for
+// `fn Class(T:! type).F(n: i32)` we will push the scope for `Class(T:! type)`
+// between the scope containing the declaration of `T` and the scope
+// containing the declaration of `n`.
+static auto PushNameQualifierScope(Context& context,
+                                   SemIR::InstId scope_inst_id,
+                                   SemIR::NameScopeId scope_id,
+                                   bool has_error = false) -> void {
+  // If the qualifier has no parameters, we don't need to keep around a
+  // parameter scope.
+  context.scope_stack().PopIfEmpty();
+
+  context.scope_stack().Push(scope_inst_id, scope_id, has_error);
 
+  // Enter a parameter scope in case the qualified name itself has parameters.
+  context.scope_stack().Push();
+}
+
+auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
   auto& name_context = decl_name_stack_.back();
-  ApplyAndLookupName(name_context, name.name_loc_id, name.name_id,
-                     /*is_unqualified=*/false);
+  ApplyAndLookupName(name_context, name.name_loc_id, name.name_id);
   name_context.has_qualifiers = true;
-  if (!CheckValidAsQualifier(name_context)) {
+
+  // Resolve the qualifier as a scope and enter the new scope.
+  auto scope_id = ResolveAsScope(name_context, name);
+  if (scope_id.is_valid()) {
+    PushNameQualifierScope(*context_, name_context.resolved_inst_id, scope_id,
+                           context_->name_scopes().Get(scope_id).has_error);
+    name_context.parent_scope_id = scope_id;
+  } else {
     name_context.state = NameContext::State::Error;
   }
 }
 
 auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
                                        SemIR::LocId loc_id,
-                                       SemIR::NameId name_id,
-                                       bool is_unqualified) -> void {
+                                       SemIR::NameId name_id) -> void {
   // The location of the name is the location of the last name token we've
   // processed so far.
   name_context.loc_id = loc_id;
@@ -215,165 +234,167 @@ auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
     // Invalid indicates an unresolved name. Store it and return.
     name_context.unresolved_name_id = name_id;
     name_context.state = NameContext::State::Unresolved;
-    return;
   } else {
     // Store the resolved instruction and continue for the target scope
     // update.
     name_context.resolved_inst_id = resolved_inst_id;
+    name_context.state = NameContext::State::Resolved;
   }
+}
+
+// Checks and returns whether name_context, which is used as a name qualifier,
+// was successfully resolved. Issues a suitable diagnostic if not.
+static auto CheckQualifierIsResolved(
+    Context& context, const DeclNameStack::NameContext& name_context) -> bool {
+  switch (name_context.state) {
+    case DeclNameStack::NameContext::State::Empty:
+      CARBON_FATAL() << "No qualifier to resolve";
 
-  // Enter the scope of the existing entity.
-  UpdateScopeIfNeeded(name_context, is_unqualified);
+    case DeclNameStack::NameContext::State::Resolved:
+      return true;
+
+    case DeclNameStack::NameContext::State::Unresolved:
+      // Because more qualifiers were found, we diagnose that the earlier
+      // qualifier failed to resolve.
+      context.DiagnoseNameNotFound(name_context.loc_id,
+                                   name_context.unresolved_name_id);
+      return false;
+
+    case DeclNameStack::NameContext::State::Finished:
+      CARBON_FATAL() << "Added a qualifier after calling FinishName";
+
+    case DeclNameStack::NameContext::State::Error:
+      // Already in an error state, so return without examining.
+      return false;
+  }
 }
 
-// Push a scope corresponding to a name qualifier. For example, for
-//
-//   fn Class(T:! type).F(n: i32)
-//
-// we will push the scope for `Class(T:! type)` between the scope containing the
-// declaration of `T` and the scope containing the declaration of `n`.
-static auto PushNameQualifierScope(Context& context,
-                                   SemIR::InstId scope_inst_id,
-                                   SemIR::NameScopeId scope_id,
-                                   bool has_error = false) -> void {
-  // If the qualifier has no parameters, we don't need to keep around a
-  // parameter scope.
-  context.scope_stack().PopIfEmpty();
+// Diagnose that a qualified declaration name specifies an incomplete class as
+// its scope.
+static auto DiagnoseQualifiedDeclInIncompleteClassScope(Context& context,
+                                                        SemIRLoc loc,
+                                                        SemIR::ClassId class_id)
+    -> void {
+  CARBON_DIAGNOSTIC(QualifiedDeclInIncompleteClassScope, Error,
+                    "Cannot declare a member of incomplete class `{0}`.",
+                    SemIR::TypeId);
+  auto builder =
+      context.emitter().Build(loc, QualifiedDeclInIncompleteClassScope,
+                              context.classes().Get(class_id).self_type_id);
+  context.NoteIncompleteClass(class_id, builder);
+  builder.Emit();
+}
 
-  context.scope_stack().Push(scope_inst_id, scope_id, has_error);
+// Diagnose that a qualified declaration name specifies an undefined interface
+// as its scope.
+static auto DiagnoseQualifiedDeclInUndefinedInterfaceScope(
+    Context& context, SemIRLoc loc, SemIR::InterfaceId interface_id,
+    SemIR::InstId interface_inst_id) -> void {
+  CARBON_DIAGNOSTIC(QualifiedDeclInUndefinedInterfaceScope, Error,
+                    "Cannot declare a member of undefined interface `{0}`.",
+                    std::string);
+  auto builder = context.emitter().Build(
+      loc, QualifiedDeclInUndefinedInterfaceScope,
+      context.sem_ir().StringifyTypeExpr(
+          context.sem_ir().constant_values().Get(interface_inst_id).inst_id()));
+  context.NoteUndefinedInterface(interface_id, builder);
+  builder.Emit();
+}
 
-  // Enter a parameter scope in case the qualified name itself has parameters.
-  context.scope_stack().Push();
+// Diagnose that a qualified declaration name specifies a different package as
+// its scope.
+static auto DiagnoseQualifiedDeclInImportedPackage(Context& context,
+                                                   SemIRLoc use_loc,
+                                                   SemIRLoc import_loc)
+    -> void {
+  CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackage, Error,
+                    "Imported packages cannot be used for declarations.");
+  CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackageSource, Note,
+                    "Package imported here.");
+  context.emitter()
+      .Build(use_loc, QualifiedDeclOutsidePackage)
+      .Note(import_loc, QualifiedDeclOutsidePackageSource)
+      .Emit();
+}
+
+// Diagnose that a qualified declaration name specifies a non-scope entity as
+// its scope.
+static auto DiagnoseQualifiedDeclInNonScope(Context& context, SemIRLoc use_loc,
+                                            SemIRLoc non_scope_entity_loc)
+    -> void {
+  CARBON_DIAGNOSTIC(QualifiedNameInNonScope, Error,
+                    "Name qualifiers are only allowed for entities that "
+                    "provide a scope.");
+  CARBON_DIAGNOSTIC(QualifiedNameNonScopeEntity, Note,
+                    "Referenced non-scope entity declared here.");
+  context.emitter()
+      .Build(use_loc, QualifiedNameInNonScope)
+      .Note(non_scope_entity_loc, QualifiedNameNonScopeEntity)
+      .Emit();
 }
 
-auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context,
-                                        bool is_unqualified) -> void {
-  // This will only be reached for resolved instructions. We update the target
-  // scope based on the resolved type.
+auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
+                                   const NameComponent& name) const
+    -> SemIR::NameScopeId {
+  if (!CheckQualifierIsResolved(*context_, name_context)) {
+    return SemIR::NameScopeId::Invalid;
+  }
+
+  auto new_params =
+      DeclParams(name.name_loc_id, name.implicit_params_id, name.params_id);
+
+  // Find the scope corresponding to the resolved instruction.
   CARBON_KIND_SWITCH(context_->insts().Get(name_context.resolved_inst_id)) {
-    case CARBON_KIND(SemIR::ClassDecl resolved_inst): {
-      const auto& class_info = context_->classes().Get(resolved_inst.class_id);
-      if (class_info.is_defined()) {
-        name_context.state = NameContext::State::Resolved;
-        name_context.parent_scope_id = class_info.scope_id;
-        if (!is_unqualified) {
-          PushNameQualifierScope(*context_, name_context.resolved_inst_id,
-                                 class_info.scope_id);
-        }
-      } else {
-        name_context.state = NameContext::State::ResolvedNonScope;
+    case CARBON_KIND(SemIR::ClassDecl class_decl): {
+      const auto& class_info = context_->classes().Get(class_decl.class_id);
+      if (!CheckRedeclParamsMatch(*context_, new_params,
+                                  DeclParams(class_info))) {
+        return SemIR::NameScopeId::Invalid;
       }
-      break;
+      if (!class_info.is_defined()) {
+        DiagnoseQualifiedDeclInIncompleteClassScope(
+            *context_, name_context.loc_id, class_decl.class_id);
+        return SemIR::NameScopeId::Invalid;
+      }
+      return class_info.scope_id;
     }
-    case CARBON_KIND(SemIR::InterfaceDecl resolved_inst): {
+    case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
       const auto& interface_info =
-          context_->interfaces().Get(resolved_inst.interface_id);
-      if (interface_info.is_defined()) {
-        name_context.state = NameContext::State::Resolved;
-        name_context.parent_scope_id = interface_info.scope_id;
-        if (!is_unqualified) {
-          PushNameQualifierScope(*context_, name_context.resolved_inst_id,
-                                 interface_info.scope_id);
-        }
-      } else {
-        name_context.state = NameContext::State::ResolvedNonScope;
+          context_->interfaces().Get(interface_decl.interface_id);
+      if (!CheckRedeclParamsMatch(*context_, new_params,
+                                  DeclParams(interface_info))) {
+        return SemIR::NameScopeId::Invalid;
       }
-      break;
+      if (!interface_info.is_defined()) {
+        DiagnoseQualifiedDeclInUndefinedInterfaceScope(
+            *context_, name_context.loc_id, interface_decl.interface_id,
+            name_context.resolved_inst_id);
+        return SemIR::NameScopeId::Invalid;
+      }
+      return interface_info.scope_id;
     }
     case CARBON_KIND(SemIR::Namespace resolved_inst): {
       auto scope_id = resolved_inst.name_scope_id;
-      name_context.state = NameContext::State::Resolved;
-      name_context.parent_scope_id = scope_id;
       auto& scope = context_->name_scopes().Get(scope_id);
+      if (!CheckRedeclParamsMatch(*context_, new_params,
+                                  DeclParams(name_context.resolved_inst_id,
+                                             SemIR::InstBlockId::Invalid,
+                                             SemIR::InstBlockId::Invalid))) {
+        return SemIR::NameScopeId::Invalid;
+      }
       if (scope.is_closed_import) {
-        CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackage, Error,
-                          "Imported packages cannot be used for declarations.");
-        CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackageSource, Note,
-                          "Package imported here.");
-        context_->emitter()
-            .Build(name_context.loc_id, QualifiedDeclOutsidePackage)
-            .Note(scope.inst_id, QualifiedDeclOutsidePackageSource)
-            .Emit();
-        // Only error once per package.
+        DiagnoseQualifiedDeclInImportedPackage(*context_, name_context.loc_id,
+                                               scope.inst_id);
+        // Only error once per package. Recover by allowing this package name to
+        // be used as a name qualifier.
         scope.is_closed_import = false;
       }
-      if (!is_unqualified) {
-        PushNameQualifierScope(*context_, name_context.resolved_inst_id,
-                               scope_id,
-                               context_->name_scopes().Get(scope_id).has_error);
-      }
-      break;
+      return scope_id;
     }
-    default:
-      name_context.state = NameContext::State::ResolvedNonScope;
-      break;
-  }
-}
-
-auto DeclNameStack::CheckValidAsQualifier(const NameContext& name_context)
-    -> bool {
-  switch (name_context.state) {
-    case NameContext::State::Error:
-      // Already in an error state, so return without examining.
-      return false;
-
-    case NameContext::State::Resolved:
-      return true;
-
-    case NameContext::State::Empty:
-      CARBON_FATAL() << "No qualifier to resolve";
-
-    case NameContext::State::Finished:
-      CARBON_FATAL() << "Added a qualifier after calling FinishName";
-
-    case NameContext::State::Unresolved:
-      // Because more qualifiers were found, we diagnose that the earlier
-      // qualifier failed to resolve.
-      context_->DiagnoseNameNotFound(name_context.loc_id,
-                                     name_context.unresolved_name_id);
-      return false;
-
-    case NameContext::State::ResolvedNonScope: {
-      // Because more qualifiers were found, we diagnose that the earlier
-      // qualifier didn't resolve to a scoped entity.
-      if (auto class_decl = context_->insts().TryGetAs<SemIR::ClassDecl>(
-              name_context.resolved_inst_id)) {
-        CARBON_DIAGNOSTIC(QualifiedDeclInIncompleteClassScope, Error,
-                          "Cannot declare a member of incomplete class `{0}`.",
-                          SemIR::TypeId);
-        auto builder = context_->emitter().Build(
-            name_context.loc_id, QualifiedDeclInIncompleteClassScope,
-            context_->classes().Get(class_decl->class_id).self_type_id);
-        context_->NoteIncompleteClass(class_decl->class_id, builder);
-        builder.Emit();
-      } else if (auto interface_decl =
-                     context_->insts().TryGetAs<SemIR::InterfaceDecl>(
-                         name_context.resolved_inst_id)) {
-        CARBON_DIAGNOSTIC(
-            QualifiedDeclInUndefinedInterfaceScope, Error,
-            "Cannot declare a member of undefined interface `{0}`.",
-            std::string);
-        auto builder = context_->emitter().Build(
-            name_context.loc_id, QualifiedDeclInUndefinedInterfaceScope,
-            context_->sem_ir().StringifyTypeExpr(
-                context_->sem_ir()
-                    .constant_values()
-                    .Get(name_context.resolved_inst_id)
-                    .inst_id()));
-        context_->NoteUndefinedInterface(interface_decl->interface_id, builder);
-        builder.Emit();
-      } else {
-        CARBON_DIAGNOSTIC(QualifiedNameInNonScope, Error,
-                          "Name qualifiers are only allowed for entities that "
-                          "provide a scope.");
-        CARBON_DIAGNOSTIC(QualifiedNameNonScopeEntity, Note,
-                          "Referenced non-scope entity declared here.");
-        context_->emitter()
-            .Build(name_context.loc_id, QualifiedNameInNonScope)
-            .Note(name_context.resolved_inst_id, QualifiedNameNonScopeEntity)
-            .Emit();
-      }
-      return false;
+    default: {
+      DiagnoseQualifiedDeclInNonScope(*context_, name_context.loc_id,
+                                      name_context.resolved_inst_id);
+      return SemIR::NameScopeId::Invalid;
     }
   }
 }

+ 8 - 19
toolchain/check/decl_name_stack.h

@@ -70,15 +70,9 @@ class DeclNameStack {
       // A context that has not processed any parts of the qualifier.
       Empty,
 
-      // An instruction ID has been resolved, whether through an identifier or
-      // expression. This provided a new scope, such as a type.
+      // The name has been resolved to an instruction ID.
       Resolved,
 
-      // An instruction ID has been resolved, whether through an identifier or
-      // expression. It did not provide a new scope, so must be the final part,
-      // such as an out-of-line function definition.
-      ResolvedNonScope,
-
       // An identifier didn't resolve.
       Unresolved,
 
@@ -231,18 +225,13 @@ class DeclNameStack {
   // Appends a name to the given name context, and performs a lookup to find
   // what, if anything, the name refers to.
   auto ApplyAndLookupName(NameContext& name_context, SemIR::LocId loc_id,
-                          SemIR::NameId name_id, bool is_unqualified) -> void;
-
-  // Checks and returns whether the given name context can be used as a
-  // qualifier. A suitable diagnostic is issued if not.
-  auto CheckValidAsQualifier(const NameContext& name_context) -> bool;
-
-  // Updates the scope on name_context as needed. This is called after
-  // resolution is complete, whether for Name or expression. When updating for
-  // an unqualified name, the resolution is noted without pushing scopes; it's
-  // instead expected this will become a name conflict.
-  auto UpdateScopeIfNeeded(NameContext& name_context, bool is_unqualified)
-      -> void;
+                          SemIR::NameId name_id) -> void;
+
+  // Attempts to resolve the given name context as a scope, and returns the
+  // corresponding scope. Issues a suitable diagnostic and returns Invalid if
+  // the name doesn't resolve to a scope.
+  auto ResolveAsScope(const NameContext& name_context,
+                      const NameComponent& name) const -> SemIR::NameScopeId;
 
   // The linked context.
   Context* context_;

+ 1 - 1
toolchain/check/handle_class.cpp

@@ -58,7 +58,7 @@ static auto MergeClassRedecl(Context& context, SemIRLoc new_loc,
 
   // Check the generic parameters match, if they were specified.
   if (!CheckRedeclParamsMatch(context, DeclParams(new_class),
-                              DeclParams(prev_class), {})) {
+                              DeclParams(prev_class))) {
     return false;
   }
 

+ 20 - 9
toolchain/check/handle_interface.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/interface.h"
+#include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -28,10 +29,6 @@ static auto BuildInterfaceDecl(Context& context,
                                Parse::AnyInterfaceDeclId node_id)
     -> std::tuple<SemIR::InterfaceId, SemIR::InstId> {
   auto name = PopNameComponent(context);
-  if (name.params_id.is_valid() || name.implicit_params_id.is_valid()) {
-    context.TODO(node_id, "generic interface");
-  }
-
   auto name_context = context.decl_name_stack().FinishName(name);
   context.node_stack()
       .PopAndDiscardSoloNodeId<Parse::NodeKind::InterfaceIntroducer>();
@@ -63,11 +60,20 @@ static auto BuildInterfaceDecl(Context& context,
   if (existing_id.is_valid()) {
     if (auto existing_interface_decl =
             context.insts().Get(existing_id).TryAs<SemIR::InterfaceDecl>()) {
-      // This is a redeclaration of an existing interface.
-      interface_decl.interface_id = existing_interface_decl->interface_id;
-
-      // TODO: Check that the generic parameter list agrees with the prior
-      // declaration.
+      // TODO: Implement full redeclaration checking. See `MergeClassDecl`. For
+      // now we just check the generic parameters match.
+      if (CheckRedeclParamsMatch(
+              context,
+              DeclParams(interface_decl_id, name.implicit_params_id,
+                         name.params_id),
+              DeclParams(context.interfaces().Get(
+                  existing_interface_decl->interface_id)))) {
+        // This is a redeclaration of an existing interface.
+        interface_decl.interface_id = existing_interface_decl->interface_id;
+        // TODO: If the new declaration is a definition, keep its parameter
+        // and implicit parameter lists rather than the ones from the
+        // previous declaration.
+      }
     } else {
       // This is a redeclaration of something other than a interface.
       context.DiagnoseDuplicateName(interface_decl_id, existing_id);
@@ -83,9 +89,14 @@ static auto BuildInterfaceDecl(Context& context,
     interface_decl.interface_id = context.interfaces().Add(
         {.name_id = name_context.name_id_for_new_inst(),
          .parent_scope_id = name_context.parent_scope_id_for_new_inst(),
+         .implicit_param_refs_id = name.implicit_params_id,
+         .param_refs_id = name.params_id,
          .decl_id = interface_decl_id});
   }
 
+  // TODO: For a generic interface declaration, set the `type_id` to a suitable
+  // generic interface type rather than `type`.
+
   // Write the interface ID into the InterfaceDecl.
   context.ReplaceInstBeforeConstantUse(interface_decl_id, interface_decl);
 

+ 12 - 4
toolchain/check/handle_namespace.cpp

@@ -45,10 +45,18 @@ auto HandleNamespace(Context& context, Parse::NamespaceId node_id) -> bool {
     // previous declaration. Otherwise, diagnose the issue.
     if (auto existing =
             context.insts().TryGetAs<SemIR::Namespace>(existing_inst_id)) {
-      // When the name conflict is an imported namespace, fill the location ID
-      // so that future diagnostics point at this declaration.
-      if (existing->import_id.is_valid() &&
-          !context.insts().GetLocId(existing_inst_id).is_valid()) {
+      if (context.name_scopes().Get(existing->name_scope_id).is_closed_import) {
+        // The existing name is a package name, so this is a name conflict.
+        context.DiagnoseDuplicateName(namespace_id, existing_inst_id);
+
+        // Treat this as a local namespace name from now on to avoid further
+        // diagnostics.
+        context.name_scopes().Get(existing->name_scope_id).is_closed_import =
+            false;
+      } else if (existing->import_id.is_valid() &&
+                 !context.insts().GetLocId(existing_inst_id).is_valid()) {
+        // When the name conflict is an imported namespace, fill the location ID
+        // so that future diagnostics point at this declaration.
         context.SetNamespaceNodeId(existing_inst_id, node_id);
       }
     } else {

+ 23 - 1
toolchain/check/import_ref.cpp

@@ -832,6 +832,9 @@ class ImportRefResolver {
 
   auto TryResolveTypedInst(SemIR::ClassDecl inst,
                            SemIR::ConstantId class_const_id) -> ResolveResult {
+    // TODO: The handling of interfaces repeats a lot with the handling of
+    // classes, and will likely also be repeated for named constraints and
+    // choice types. Factor out some of this functionality.
     const auto& import_class = import_ir_.classes().Get(inst.class_id);
 
     SemIR::ClassId class_id = SemIR::ClassId::Invalid;
@@ -1070,8 +1073,19 @@ class ImportRefResolver {
     // Start with an incomplete interface.
     SemIR::Interface new_interface = {
         .name_id = GetLocalNameId(import_interface.name_id),
-        // Set in the second pass once we've imported it.
+        // These are set in the second pass once we've imported them. Import
+        // enough of the parameter lists that we know whether this interface is
+        // a generic interface and can build the right constant value for it.
+        // TODO: Add a better way to represent a generic `Interface` prior to
+        // importing the parameters.
         .parent_scope_id = SemIR::NameScopeId::Invalid,
+        .implicit_param_refs_id =
+            import_interface.implicit_param_refs_id.is_valid()
+                ? SemIR::InstBlockId::Empty
+                : SemIR::InstBlockId::Invalid,
+        .param_refs_id = import_interface.param_refs_id.is_valid()
+                             ? SemIR::InstBlockId::Empty
+                             : SemIR::InstBlockId::Invalid,
         .decl_id = interface_decl_id,
     };
 
@@ -1122,6 +1136,10 @@ class ImportRefResolver {
 
     auto parent_scope_id =
         GetLocalNameScopeId(import_interface.parent_scope_id);
+    llvm::SmallVector<SemIR::ConstantId> implicit_param_const_ids =
+        GetLocalParamConstantIds(import_interface.implicit_param_refs_id);
+    llvm::SmallVector<SemIR::ConstantId> param_const_ids =
+        GetLocalParamConstantIds(import_interface.param_refs_id);
     auto self_param_id = GetLocalConstantId(import_interface.self_param_id);
 
     if (HasNewWork(initial_work)) {
@@ -1133,6 +1151,10 @@ class ImportRefResolver {
             .GetAs<SemIR::InterfaceType>(interface_const_id.inst_id())
             .interface_id);
     new_interface.parent_scope_id = parent_scope_id;
+    new_interface.implicit_param_refs_id = GetLocalParamRefsId(
+        import_interface.implicit_param_refs_id, implicit_param_const_ids);
+    new_interface.param_refs_id =
+        GetLocalParamRefsId(import_interface.param_refs_id, param_const_ids);
 
     if (import_interface.is_defined()) {
       AddInterfaceDefinition(import_interface, new_interface, self_param_id);

+ 10 - 10
toolchain/check/merge.cpp

@@ -210,9 +210,9 @@ static auto CheckRedeclParam(Context& context,
 }
 
 // Returns false if the param refs differ for a redeclaration.
-static auto CheckRedeclParams(Context& context, SemIR::InstId new_decl_id,
+static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
                               SemIR::InstBlockId new_param_refs_id,
-                              SemIR::InstId prev_decl_id,
+                              SemIRLoc prev_decl_loc,
                               SemIR::InstBlockId prev_param_refs_id,
                               llvm::StringLiteral param_diag_label,
                               Substitutions substitutions) -> bool {
@@ -231,9 +231,9 @@ static auto CheckRedeclParams(Context& context, SemIR::InstId new_decl_id,
                       llvm::StringLiteral, llvm::StringLiteral);
     context.emitter()
         .Build(
-            new_decl_id, RedeclParamListDiffers, param_diag_label,
+            new_decl_loc, RedeclParamListDiffers, param_diag_label,
             new_param_refs_id.is_valid() ? llvm::StringLiteral("") : "missing ")
-        .Note(prev_decl_id, RedeclParamListPrevious, param_diag_label,
+        .Note(prev_decl_loc, RedeclParamListPrevious, param_diag_label,
               prev_param_refs_id.is_valid() ? llvm::StringLiteral("") : "out")
         .Emit();
     return false;
@@ -251,9 +251,9 @@ static auto CheckRedeclParams(Context& context, SemIR::InstId new_decl_id,
                       "Previously declared with {0}parameter count of {1}.",
                       llvm::StringLiteral, int32_t);
     context.emitter()
-        .Build(new_decl_id, RedeclParamCountDiffers, param_diag_label,
+        .Build(new_decl_loc, RedeclParamCountDiffers, param_diag_label,
                new_param_ref_ids.size())
-        .Note(prev_decl_id, RedeclParamCountPrevious, param_diag_label,
+        .Note(prev_decl_loc, RedeclParamCountPrevious, param_diag_label,
               prev_param_ref_ids.size())
         .Emit();
     return false;
@@ -275,12 +275,12 @@ auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
       EntityHasParamError(context, prev_entity)) {
     return false;
   }
-  if (!CheckRedeclParams(context, new_entity.decl_id,
-                         new_entity.implicit_param_refs_id, prev_entity.decl_id,
+  if (!CheckRedeclParams(context, new_entity.loc,
+                         new_entity.implicit_param_refs_id, prev_entity.loc,
                          prev_entity.implicit_param_refs_id, "implicit ",
                          substitutions) ||
-      !CheckRedeclParams(context, new_entity.decl_id, new_entity.param_refs_id,
-                         prev_entity.decl_id, prev_entity.param_refs_id, "",
+      !CheckRedeclParams(context, new_entity.loc, new_entity.param_refs_id,
+                         prev_entity.loc, prev_entity.param_refs_id, "",
                          substitutions)) {
     return false;
   }

+ 11 - 4
toolchain/check/merge.h

@@ -46,12 +46,18 @@ auto ReplacePrevInstForMerge(Context& context, SemIR::NameScopeId scope_id,
 struct DeclParams {
   template <typename Entity>
   explicit DeclParams(const Entity& entity)
-      : decl_id(entity.decl_id),
+      : loc(entity.decl_id),
         implicit_param_refs_id(entity.implicit_param_refs_id),
         param_refs_id(entity.param_refs_id) {}
 
-  // The declaration of the entity.
-  SemIR::InstId decl_id;
+  DeclParams(SemIRLoc loc, SemIR::InstBlockId implicit_params_id,
+             SemIR::InstBlockId params_id)
+      : loc(loc),
+        implicit_param_refs_id(implicit_params_id),
+        param_refs_id(params_id) {}
+
+  // The location of the declaration of the entity.
+  SemIRLoc loc;
   // The implicit parameters of the entity. Can be Invalid if there is no
   // implicit parameter list.
   SemIR::InstBlockId implicit_param_refs_id;
@@ -65,7 +71,8 @@ struct DeclParams {
 // returns false.
 auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
                             const DeclParams& prev_entity,
-                            Substitutions substitutions) -> bool;
+                            Substitutions substitutions = Substitutions())
+    -> bool;
 
 }  // namespace Carbon::Check
 

+ 105 - 0
toolchain/check/testdata/class/fail_generic_method.carbon

@@ -0,0 +1,105 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/fail_generic_method.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/fail_generic_method.carbon
+
+class Class(T:! type) {
+  var a: T;
+  fn F[self: Self](n: T);
+}
+
+// TODO: The follow-on errors here aren't great. Investigate whether we can
+// enter the scope anyway if the parameters don't match.
+// CHECK:STDERR: fail_generic_method.carbon:[[@LINE+14]]:10: ERROR: Redeclaration differs at parameter 1.
+// CHECK:STDERR: fn Class(N:! i32).F[self: Self](n: T) {}
+// CHECK:STDERR:          ^
+// CHECK:STDERR: fail_generic_method.carbon:[[@LINE-10]]:13: Previous declaration's corresponding parameter here.
+// CHECK:STDERR: class Class(T:! type) {
+// CHECK:STDERR:             ^
+// CHECK:STDERR:
+// CHECK:STDERR: fail_generic_method.carbon:[[@LINE+7]]:27: ERROR: Name `Self` not found.
+// CHECK:STDERR: fn Class(N:! i32).F[self: Self](n: T) {}
+// CHECK:STDERR:                           ^~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_generic_method.carbon:[[@LINE+3]]:36: ERROR: Name `T` not found.
+// CHECK:STDERR: fn Class(N:! i32).F[self: Self](n: T) {}
+// CHECK:STDERR:                                    ^
+fn Class(N:! i32).F[self: Self](n: T) {}
+
+// CHECK:STDOUT: --- fail_generic_method.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type Class, T [symbolic]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = struct_type {.a: T} [symbolic]
+// CHECK:STDOUT:   %Int32: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %struct.3: Int32 = struct_value () [template]
+// CHECK:STDOUT:   %N: i32 = bind_symbolic_name N 0 [symbolic]
+// CHECK:STDOUT:   %.4: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.4: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Class = %Class.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc11_13.1: type = param T
+// CHECK:STDOUT:     %T.loc11_13.2: type = bind_symbolic_name T 0, %T.loc11_13.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: Int32 = import_ref ir3, inst+3, loaded [template = constants.%struct.3]
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.4] {
+// CHECK:STDOUT:     %int.make_type_32: init type = call constants.%struct.3() [template = i32]
+// CHECK:STDOUT:     %.loc32_14.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc32_14.2: type = converted %int.make_type_32, %.loc32_14.1 [template = i32]
+// CHECK:STDOUT:     %N.loc32_10.1: i32 = param N
+// CHECK:STDOUT:     %N.loc32_10.2: i32 = bind_symbolic_name N 0, %N.loc32_10.1 [symbolic = constants.%N]
+// CHECK:STDOUT:     %Self.ref: <error> = name_ref Self, <error> [template = <error>]
+// CHECK:STDOUT:     %self.loc32_21.1: <error> = param self
+// CHECK:STDOUT:     @.1.%self: <error> = bind_name self, %self.loc32_21.1
+// CHECK:STDOUT:     %T.ref: <error> = name_ref T, <error> [template = <error>]
+// CHECK:STDOUT:     %n.loc32_33.1: <error> = param n
+// CHECK:STDOUT:     @.1.%n: <error> = bind_name n, %n.loc32_33.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:   %.loc12: <unbound element of class Class> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
+// CHECK:STDOUT:     %self.loc13_8.1: Class = param self
+// CHECK:STDOUT:     %self.loc13_8.2: Class = bind_name self, %self.loc13_8.1
+// CHECK:STDOUT:     %T.ref.loc13: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %n.loc13_20.1: T = param n
+// CHECK:STDOUT:     %n.loc13_20.2: T = bind_name n, %n.loc13_20.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Class.2
+// CHECK:STDOUT:   .a = %.loc12
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[@Class.%self.loc13_8.2: Class](@Class.%n.loc13_20.2: T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1[%self: <error>](%n: <error>) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 0 - 75
toolchain/check/testdata/class/generic/fail_todo_member_out_of_line.carbon

@@ -1,75 +0,0 @@
-// 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/fail_todo_member_out_of_line.carbon
-// TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/fail_todo_member_out_of_line.carbon
-
-class Class(T:! type) {
-  fn F(n: T) -> T;
-}
-
-// CHECK:STDERR: fail_todo_member_out_of_line.carbon:[[@LINE+3]]:9: ERROR: Semantics TODO: `name qualifier with parameters`.
-// CHECK:STDERR: fn Class(T:! type).F(n: T) -> T {
-// CHECK:STDERR:         ^~~~~~~~~~
-fn Class(T:! type).F(n: T) -> T {
-  return n;
-}
-
-// CHECK:STDOUT: --- fail_todo_member_out_of_line.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
-// CHECK:STDOUT:   %.1: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
-// CHECK:STDOUT:   %F: type = fn_type @F [template]
-// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {
-// CHECK:STDOUT:     .Core = %Core
-// CHECK:STDOUT:     .Class = %Class.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
-// CHECK:STDOUT:     %T.loc11_13.1: type = param T
-// CHECK:STDOUT:     %T.loc11_13.2: type = bind_symbolic_name T 0, %T.loc11_13.1 [symbolic = constants.%T]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
-// CHECK:STDOUT:     %T.loc18_10.1: type = param T
-// CHECK:STDOUT:     %T.loc18_10.2: type = bind_symbolic_name T 0, %T.loc18_10.1 [symbolic = constants.%T]
-// CHECK:STDOUT:     %T.ref.loc18_25: type = name_ref T, %T.loc18_10.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %n.loc18_22.1: T = param n
-// CHECK:STDOUT:     @F.%n: T = bind_name n, %n.loc18_22.1
-// CHECK:STDOUT:     %T.ref.loc18_31: type = name_ref T, %T.loc18_10.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     @F.%return: ref T = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @Class {
-// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
-// CHECK:STDOUT:     %T.ref.loc12_11: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %n.loc12_8.1: T = param n
-// CHECK:STDOUT:     %n.loc12_8.2: T = bind_name n, %n.loc12_8.1
-// CHECK:STDOUT:     %T.ref.loc12_17: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %return.var: ref T = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class.2
-// CHECK:STDOUT:   .F = %F.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%n: T) -> T {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %n.ref: T = name_ref n, %n
-// CHECK:STDOUT:   return %n.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 412 - 0
toolchain/check/testdata/class/generic/member_out_of_line.carbon

@@ -0,0 +1,412 @@
+// 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/member_out_of_line.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/member_out_of_line.carbon
+
+// --- basic.carbon
+
+library "basic";
+
+class Class(T:! type) {
+  fn F(n: T) -> T;
+}
+
+fn Class(T:! type).F(n: T) -> T {
+  return n;
+}
+
+// --- nested.carbon
+
+library "nested";
+
+class A(T:! type) {
+  class B(N:! T) {
+    fn F[self: Self](a: T);
+  }
+}
+
+fn A(T:! type).B(N:! T).F[self: Self](a: T) {}
+
+// --- fail_mismatched_not_generic_vs_generic.carbon
+
+library "fail_mismatched_not_generic_vs_generic";
+
+class NotGeneric {
+  fn F();
+}
+
+// CHECK:STDERR: fail_mismatched_not_generic_vs_generic.carbon:[[@LINE+7]]:4: ERROR: Redeclaration differs because of parameter list.
+// CHECK:STDERR: fn NotGeneric(T:! type).F() {}
+// CHECK:STDERR:    ^~~~~~~~~~
+// CHECK:STDERR: fail_mismatched_not_generic_vs_generic.carbon:[[@LINE-7]]:1: Previously declared without parameter list.
+// CHECK:STDERR: class NotGeneric {
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn NotGeneric(T:! type).F() {}
+
+// --- fail_mismatched_too_few_args.carbon
+
+library "fail_mismatched_too_few_args";
+
+class Generic(T:! type) {
+  fn TooFew();
+}
+
+// CHECK:STDERR: fail_mismatched_too_few_args.carbon:[[@LINE+7]]:4: ERROR: Redeclaration differs because of parameter count of 0.
+// CHECK:STDERR: fn Generic().TooFew() {}
+// CHECK:STDERR:    ^~~~~~~
+// CHECK:STDERR: fail_mismatched_too_few_args.carbon:[[@LINE-7]]:1: Previously declared with parameter count of 1.
+// CHECK:STDERR: class Generic(T:! type) {
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn Generic().TooFew() {}
+
+// --- fail_mismatched_too_many_args.carbon
+
+library "fail_mismatched_too_many_args";
+
+class Generic(T:! type) {
+  fn TooMany();
+}
+
+// CHECK:STDERR: fail_mismatched_too_many_args.carbon:[[@LINE+7]]:4: ERROR: Redeclaration differs because of parameter count of 2.
+// CHECK:STDERR: fn Generic(T:! type, U:! type).TooMany() {}
+// CHECK:STDERR:    ^~~~~~~
+// CHECK:STDERR: fail_mismatched_too_many_args.carbon:[[@LINE-7]]:1: Previously declared with parameter count of 1.
+// CHECK:STDERR: class Generic(T:! type) {
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn Generic(T:! type, U:! type).TooMany() {}
+
+// --- fail_mismatched_wrong_arg_type.carbon
+
+library "fail_mismatched_wrong_arg_type";
+
+class Generic(T:! type) {
+  fn WrongType();
+}
+
+// CHECK:STDERR: fail_mismatched_wrong_arg_type.carbon:[[@LINE+6]]:12: ERROR: Redeclaration differs at parameter 1.
+// CHECK:STDERR: fn Generic(T:! ()).WrongType() {}
+// CHECK:STDERR:            ^
+// CHECK:STDERR: fail_mismatched_wrong_arg_type.carbon:[[@LINE-7]]:15: Previous declaration's corresponding parameter here.
+// CHECK:STDERR: class Generic(T:! type) {
+// CHECK:STDERR:               ^
+fn Generic(T:! ()).WrongType() {}
+
+// CHECK:STDOUT: --- basic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Class = %Class.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc4_13.1: type = param T
+// CHECK:STDOUT:     %T.loc4_13.2: type = bind_symbolic_name T 0, %T.loc4_13.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
+// CHECK:STDOUT:     %T.loc8_10.1: type = param T
+// CHECK:STDOUT:     %T.loc8_10.2: type = bind_symbolic_name T 0, %T.loc8_10.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.ref.loc8_25: type = name_ref T, %T.loc8_10.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %n.loc8_22.1: T = param n
+// CHECK:STDOUT:     @F.%n: T = bind_name n, %n.loc8_22.1
+// CHECK:STDOUT:     %T.ref.loc8_31: type = name_ref T, %T.loc8_10.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     @F.%return: ref T = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
+// CHECK:STDOUT:     %T.ref.loc5_11: type = name_ref T, file.%T.loc4_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %n.loc5_8.1: T = param n
+// CHECK:STDOUT:     %n.loc5_8.2: T = bind_name n, %n.loc5_8.1
+// CHECK:STDOUT:     %T.ref.loc5_17: type = name_ref T, file.%T.loc4_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %return.var: ref T = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Class.2
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%n: T) -> T {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: T = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- nested.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %A.1: type = generic_class_type @A [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: A = struct_value () [template]
+// CHECK:STDOUT:   %A.2: type = class_type @A [template]
+// CHECK:STDOUT:   %N: T = bind_symbolic_name N 1 [symbolic]
+// CHECK:STDOUT:   %B.1: type = generic_class_type @B [template]
+// CHECK:STDOUT:   %struct.2: B = struct_value () [template]
+// CHECK:STDOUT:   %B.2: type = class_type @B [template]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %struct.3: F = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: A = class_decl @A [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc4_9.1: type = param T
+// CHECK:STDOUT:     %T.loc4_9.2: type = bind_symbolic_name T 0, %T.loc4_9.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.3] {
+// CHECK:STDOUT:     %T.loc10_6.1: type = param T
+// CHECK:STDOUT:     %T.loc10_6.2: type = bind_symbolic_name T 0, %T.loc10_6.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.ref.loc10_22: type = name_ref T, %T.loc10_6.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %N.loc10_18.1: T = param N
+// CHECK:STDOUT:     %N.loc10_18.2: T = bind_symbolic_name N 1, %N.loc10_18.1 [symbolic = constants.%N]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B.2 [template = constants.%B.2]
+// CHECK:STDOUT:     %self.loc10_27.1: B = param self
+// CHECK:STDOUT:     @F.%self: B = bind_name self, %self.loc10_27.1
+// CHECK:STDOUT:     %T.ref.loc10_42: type = name_ref T, %T.loc10_6.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %a.loc10_39.1: T = param a
+// CHECK:STDOUT:     @F.%a: T = bind_name a, %a.loc10_39.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %B.decl: B = class_decl @B [template = constants.%struct.2] {
+// CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc4_9.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %N.loc5_11.1: T = param N
+// CHECK:STDOUT:     %N.loc5_11.2: T = bind_symbolic_name N 1, %N.loc5_11.1 [symbolic = constants.%N]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A.2
+// CHECK:STDOUT:   .B = %B.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.3] {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B.2 [template = constants.%B.2]
+// CHECK:STDOUT:     %self.loc6_10.1: B = param self
+// CHECK:STDOUT:     %self.loc6_10.2: B = bind_name self, %self.loc6_10.1
+// CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc4_9.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %a.loc6_22.1: T = param a
+// CHECK:STDOUT:     %a.loc6_22.2: T = bind_name a, %a.loc6_22.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B.2
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[%self: B](%a: T) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_mismatched_not_generic_vs_generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %NotGeneric: type = class_type @NotGeneric [template]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: F = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.3: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .NotGeneric = %NotGeneric.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %NotGeneric.decl: type = class_decl @NotGeneric [template = constants.%NotGeneric] {}
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.2] {
+// CHECK:STDOUT:     %T.loc15_15.1: type = param T
+// CHECK:STDOUT:     %T.loc15_15.2: type = bind_symbolic_name T 0, %T.loc15_15.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NotGeneric {
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.1] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%NotGeneric
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_mismatched_too_few_args.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Generic.1: type = generic_class_type @Generic [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Generic = struct_value () [template]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic [template]
+// CHECK:STDOUT:   %TooFew: type = fn_type @TooFew [template]
+// CHECK:STDOUT:   %struct.2: TooFew = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Generic = %Generic.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Generic.decl: Generic = class_decl @Generic [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc4_15.1: type = param T
+// CHECK:STDOUT:     %T.loc4_15.2: type = bind_symbolic_name T 0, %T.loc4_15.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Generic {
+// CHECK:STDOUT:   %TooFew.decl: TooFew = fn_decl @TooFew [template = constants.%struct.2] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Generic.2
+// CHECK:STDOUT:   .TooFew = %TooFew.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TooFew();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_mismatched_too_many_args.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Generic.1: type = generic_class_type @Generic [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Generic = struct_value () [template]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic [template]
+// CHECK:STDOUT:   %TooMany: type = fn_type @TooMany [template]
+// CHECK:STDOUT:   %struct.2: TooMany = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U 1 [symbolic]
+// CHECK:STDOUT:   %.3: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Generic = %Generic.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Generic.decl: Generic = class_decl @Generic [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc4_15.1: type = param T
+// CHECK:STDOUT:     %T.loc4_15.2: type = bind_symbolic_name T 0, %T.loc4_15.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {
+// CHECK:STDOUT:     %T.loc15_12.1: type = param T
+// CHECK:STDOUT:     %T.loc15_12.2: type = bind_symbolic_name T 0, %T.loc15_12.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %U.loc15_22.1: type = param U
+// CHECK:STDOUT:     %U.loc15_22.2: type = bind_symbolic_name U 1, %U.loc15_22.1 [symbolic = constants.%U]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Generic {
+// CHECK:STDOUT:   %TooMany.decl: TooMany = fn_decl @TooMany [template = constants.%struct.2] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Generic.2
+// CHECK:STDOUT:   .TooMany = %TooMany.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TooMany();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_mismatched_wrong_arg_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T.1: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Generic.1: type = generic_class_type @Generic [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Generic = struct_value () [template]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic [template]
+// CHECK:STDOUT:   %WrongType: type = fn_type @WrongType [template]
+// CHECK:STDOUT:   %struct.2: WrongType = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %T.2: () = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.3: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Generic = %Generic.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Generic.decl: Generic = class_decl @Generic [template = constants.%struct.1] {
+// CHECK:STDOUT:     %T.loc4_15.1: type = param T
+// CHECK:STDOUT:     %T.loc4_15.2: type = bind_symbolic_name T 0, %T.loc4_15.1 [symbolic = constants.%T.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {
+// CHECK:STDOUT:     %.loc14_17.1: () = tuple_literal ()
+// CHECK:STDOUT:     %.loc14_17.2: type = converted %.loc14_17.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:     %T.loc14_12.1: () = param T
+// CHECK:STDOUT:     %T.loc14_12.2: () = bind_symbolic_name T 0, %T.loc14_12.1 [symbolic = constants.%T.2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Generic {
+// CHECK:STDOUT:   %WrongType.decl: WrongType = fn_decl @WrongType [template = constants.%struct.2] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Generic.2
+// CHECK:STDOUT:   .WrongType = %WrongType.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @WrongType();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 10 - 13
toolchain/check/testdata/class/fail_todo_generic_method.carbon → toolchain/check/testdata/class/generic_method.carbon

@@ -4,21 +4,18 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/fail_todo_generic_method.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/generic_method.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/fail_todo_generic_method.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic_method.carbon
 
 class Class(T:! type) {
   var a: T;
   fn F[self: Self](n: T);
 }
 
-// CHECK:STDERR: fail_todo_generic_method.carbon:[[@LINE+3]]:9: ERROR: Semantics TODO: `name qualifier with parameters`.
-// CHECK:STDERR: fn Class(T:! type).F[self: Self](n: T) {}
-// CHECK:STDERR:         ^~~~~~~~~~
 fn Class(T:! type).F[self: Self](n: T) {}
 
-// CHECK:STDOUT: --- fail_todo_generic_method.carbon
+// CHECK:STDOUT: --- generic_method.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
@@ -44,14 +41,14 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:     %T.loc11_13.2: type = bind_symbolic_name T 0, %T.loc11_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
-// CHECK:STDOUT:     %T.loc19_10.1: type = param T
-// CHECK:STDOUT:     %T.loc19_10.2: type = bind_symbolic_name T 0, %T.loc19_10.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.loc16_10.1: type = param T
+// CHECK:STDOUT:     %T.loc16_10.2: type = bind_symbolic_name T 0, %T.loc16_10.1 [symbolic = constants.%T]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
-// CHECK:STDOUT:     %self.loc19_22.1: Class = param self
-// CHECK:STDOUT:     @F.%self: Class = bind_name self, %self.loc19_22.1
-// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc19_10.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %n.loc19_34.1: T = param n
-// CHECK:STDOUT:     @F.%n: T = bind_name n, %n.loc19_34.1
+// CHECK:STDOUT:     %self.loc16_22.1: Class = param self
+// CHECK:STDOUT:     @F.%self: Class = bind_name self, %self.loc16_22.1
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc16_10.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %n.loc16_34.1: T = param n
+// CHECK:STDOUT:     @F.%n: T = bind_name n, %n.loc16_34.1
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 15 - 19
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -8,10 +8,6 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
 
-// CHECK:STDERR: fail_extend_impl_forall.carbon:[[@LINE+4]]:1: ERROR: Semantics TODO: `generic interface`.
-// CHECK:STDERR: interface GenericInterface(T:! type) {
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
 interface GenericInterface(T:! type) {
   fn F(x: T);
 }
@@ -58,8 +54,8 @@ class C {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
 // CHECK:STDOUT:   %GenericInterface.decl: type = interface_decl @GenericInterface [template = constants.%.1] {
-// CHECK:STDOUT:     %T.loc15_28.1: type = param T
-// CHECK:STDOUT:     %T.loc15_28.2: type = bind_symbolic_name T 0, %T.loc15_28.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.loc11_28.1: type = param T
+// CHECK:STDOUT:     %T.loc11_28.2: type = bind_symbolic_name T 0, %T.loc11_28.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
 // CHECK:STDOUT: }
@@ -67,23 +63,23 @@ class C {
 // CHECK:STDOUT: interface @GenericInterface {
 // CHECK:STDOUT:   %Self: GenericInterface = bind_symbolic_name Self 1 [symbolic = constants.%Self]
 // CHECK:STDOUT:   %F.decl: F = fn_decl @F.1 [template = constants.%struct.1] {
-// CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc15_28.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %x.loc16_8.1: T = param x
-// CHECK:STDOUT:     %x.loc16_8.2: T = bind_name x, %x.loc16_8.1
+// CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc11_28.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %x.loc12_8.1: T = param x
+// CHECK:STDOUT:     %x.loc12_8.2: T = bind_name x, %x.loc12_8.1
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.loc16: <associated F in GenericInterface> = assoc_entity element0, %F.decl [template = constants.%.4]
+// CHECK:STDOUT:   %.loc12: <associated F in GenericInterface> = assoc_entity element0, %F.decl [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self
-// CHECK:STDOUT:   .F = %.loc16
+// CHECK:STDOUT:   .F = %.loc12
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: C as <error> {
 // CHECK:STDOUT:   %F.decl: F = fn_decl @F.2 [template = constants.%struct.2] {
-// CHECK:STDOUT:     %T.ref: type = name_ref T, @C.%T.loc31_23.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %x.loc32_10.1: T = param x
-// CHECK:STDOUT:     %x.loc32_10.2: T = bind_name x, %x.loc32_10.1
+// CHECK:STDOUT:     %T.ref: type = name_ref T, @C.%T.loc27_23.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %x.loc28_10.1: T = param x
+// CHECK:STDOUT:     %x.loc28_10.2: T = bind_name x, %x.loc28_10.1
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -93,10 +89,10 @@ class C {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   impl_decl @impl {
-// CHECK:STDOUT:     %T.loc31_23.1: type = param T
-// CHECK:STDOUT:     %T.loc31_23.2: type = bind_symbolic_name T 0, %T.loc31_23.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.loc27_23.1: type = param T
+// CHECK:STDOUT:     %T.loc27_23.2: type = bind_symbolic_name T 0, %T.loc27_23.1 [symbolic = constants.%T]
 // CHECK:STDOUT:     %GenericInterface.ref: type = name_ref GenericInterface, file.%GenericInterface.decl [template = constants.%.1]
-// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc31_23.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc27_23.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -104,9 +100,9 @@ class C {
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1(@GenericInterface.%x.loc16_8.2: T);
+// CHECK:STDOUT: fn @F.1(@GenericInterface.%x.loc12_8.2: T);
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2(@impl.%x.loc32_10.2: T) {
+// CHECK:STDOUT: fn @F.2(@impl.%x.loc28_10.2: T) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 114 - 0
toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon

@@ -0,0 +1,114 @@
+// 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/interface/no_prelude/fail_generic_redeclaration.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon
+
+interface NotGeneric;
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE+7]]:1: ERROR: Redeclaration differs because of parameter list.
+// CHECK:STDERR: interface NotGeneric(T:! type) {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE-4]]:1: Previously declared without parameter list.
+// CHECK:STDERR: interface NotGeneric;
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+interface NotGeneric(T:! type) {}
+
+interface Generic(T:! type);
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE+7]]:1: ERROR: Redeclaration differs because of missing parameter list.
+// CHECK:STDERR: interface Generic {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE-4]]:1: Previously declared with parameter list.
+// CHECK:STDERR: interface Generic(T:! type);
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+interface Generic {}
+
+interface DifferentParams(T:! type);
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE+6]]:27: ERROR: Redeclaration differs at parameter 1.
+// CHECK:STDERR: interface DifferentParams(T:! ()) {}
+// CHECK:STDERR:                           ^
+// CHECK:STDERR: fail_generic_redeclaration.carbon:[[@LINE-4]]:27: Previous declaration's corresponding parameter here.
+// CHECK:STDERR: interface DifferentParams(T:! type);
+// CHECK:STDERR:                           ^
+interface DifferentParams(T:! ()) {}
+
+// CHECK:STDOUT: --- fail_generic_redeclaration.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @NotGeneric [template]
+// CHECK:STDOUT:   %T.1: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @.1 [template]
+// CHECK:STDOUT:   %Self.1: <invalid> = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %.3: type = interface_type @Generic [template]
+// CHECK:STDOUT:   %.4: type = interface_type @.2 [template]
+// CHECK:STDOUT:   %Self.2: <invalid> = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.5: type = interface_type @DifferentParams [template]
+// CHECK:STDOUT:   %.6: type = tuple_type () [template]
+// CHECK:STDOUT:   %T.2: () = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.7: type = interface_type @.3 [template]
+// CHECK:STDOUT:   %Self.3: <invalid> = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .NotGeneric = %NotGeneric.decl
+// CHECK:STDOUT:     .Generic = %Generic.decl
+// CHECK:STDOUT:     .DifferentParams = %DifferentParams.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %NotGeneric.decl: type = interface_decl @NotGeneric [template = constants.%.1] {}
+// CHECK:STDOUT:   %.decl.loc19: type = interface_decl @.1 [template = constants.%.2] {
+// CHECK:STDOUT:     %T.loc19_22.1: type = param T
+// CHECK:STDOUT:     %T.loc19_22.2: type = bind_symbolic_name T 0, %T.loc19_22.1 [symbolic = constants.%T.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Generic.decl: type = interface_decl @Generic [template = constants.%.3] {
+// CHECK:STDOUT:     %T.loc21_19.1: type = param T
+// CHECK:STDOUT:     %T.loc21_19.2: type = bind_symbolic_name T 0, %T.loc21_19.1 [symbolic = constants.%T.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl.loc29: type = interface_decl @.2 [template = constants.%.4] {}
+// CHECK:STDOUT:   %DifferentParams.decl: type = interface_decl @DifferentParams [template = constants.%.5] {
+// CHECK:STDOUT:     %T.loc31_27.1: type = param T
+// CHECK:STDOUT:     %T.loc31_27.2: type = bind_symbolic_name T 0, %T.loc31_27.1 [symbolic = constants.%T.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl.loc38: type = interface_decl @.3 [template = constants.%.7] {
+// CHECK:STDOUT:     %.loc38_32.1: () = tuple_literal ()
+// CHECK:STDOUT:     %.loc38_32.2: type = converted %.loc38_32.1, constants.%.6 [template = constants.%.6]
+// CHECK:STDOUT:     %T.loc38_27.1: () = param T
+// CHECK:STDOUT:     %T.loc38_27.2: () = bind_symbolic_name T 0, %T.loc38_27.1 [symbolic = constants.%T.2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @NotGeneric;
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @.1 {
+// CHECK:STDOUT:   %Self: <invalid> = bind_symbolic_name Self 1 [symbolic = constants.%Self.1]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Generic;
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @.2 {
+// CHECK:STDOUT:   %Self: <invalid> = bind_symbolic_name Self 0 [symbolic = constants.%Self.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @DifferentParams;
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @.3 {
+// CHECK:STDOUT:   %Self: <invalid> = bind_symbolic_name Self 1 [symbolic = constants.%Self.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 90 - 0
toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon

@@ -0,0 +1,90 @@
+// 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/interface/no_prelude/fail_todo_generic_default_fn.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon
+
+interface I(T:! type) {
+  // TODO: Use `default` here.
+  fn F[self: Self]() -> Self;
+}
+
+// CHECK:STDERR: fail_todo_generic_default_fn.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fn I(T:! type).F[self: Self]() -> Self { return self; }
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_generic_default_fn.carbon:[[@LINE-6]]:3: Name is previously declared here.
+// CHECK:STDERR:   fn F[self: Self]() -> Self;
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn I(T:! type).F[self: Self]() -> Self { return self; }
+
+// CHECK:STDOUT: --- fail_todo_generic_default_fn.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: F = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = assoc_entity_type @I, F [template]
+// CHECK:STDOUT:   %.4: <associated F in I> = assoc_entity element0, @I.%F.decl [template]
+// CHECK:STDOUT:   %.5: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {
+// CHECK:STDOUT:     %T.loc11_13.1: type = param T
+// CHECK:STDOUT:     %T.loc11_13.2: type = bind_symbolic_name T 0, %T.loc11_13.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.2] {
+// CHECK:STDOUT:     %T.loc22_6.1: type = param T
+// CHECK:STDOUT:     %T.loc22_6.2: type = bind_symbolic_name T 0, %T.loc22_6.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %Self.ref.loc22_24: I = name_ref Self, @I.%Self [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc22_24.1: type = facet_type_access %Self.ref.loc22_24 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc22_24.2: type = converted %Self.ref.loc22_24, %.loc22_24.1 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %self.loc22_18.1: Self = param self
+// CHECK:STDOUT:     @.1.%self: Self = bind_name self, %self.loc22_18.1
+// CHECK:STDOUT:     %Self.ref.loc22_35: I = name_ref Self, @I.%Self [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc22_35.1: type = facet_type_access %Self.ref.loc22_35 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc22_35.2: type = converted %Self.ref.loc22_35, %.loc22_35.1 [symbolic = constants.%Self]
+// CHECK:STDOUT:     @.1.%return: ref Self = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self 1 [symbolic = constants.%Self]
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.1] {
+// CHECK:STDOUT:     %Self.ref.loc13_14: I = name_ref Self, %Self [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc13_14.1: type = facet_type_access %Self.ref.loc13_14 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc13_14.2: type = converted %Self.ref.loc13_14, %.loc13_14.1 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %self.loc13_8.1: Self = param self
+// CHECK:STDOUT:     %self.loc13_8.2: Self = bind_name self, %self.loc13_8.1
+// CHECK:STDOUT:     %Self.ref.loc13_25: I = name_ref Self, %Self [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc13_25.1: type = facet_type_access %Self.ref.loc13_25 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %.loc13_25.2: type = converted %Self.ref.loc13_25, %.loc13_25.1 [symbolic = constants.%Self]
+// CHECK:STDOUT:     %return.var: ref Self = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc13_29: <associated F in I> = assoc_entity element0, %F.decl [template = constants.%.4]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc13_29
+// CHECK:STDOUT:   witness = (%F.decl)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[@I.%self.loc13_8.2: Self]() -> Self;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1[%self: Self]() -> Self {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %self.ref: Self = name_ref self, %self
+// CHECK:STDOUT:   return %self.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 13 - 8
toolchain/check/testdata/interface/no_prelude/fail_todo_generic.carbon → toolchain/check/testdata/interface/no_prelude/generic.carbon

@@ -4,18 +4,17 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interface/no_prelude/fail_todo_generic.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interface/no_prelude/generic.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/fail_todo_generic.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/generic.carbon
 
-// CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+3]]:1: ERROR: Semantics TODO: `generic interface`.
-// CHECK:STDERR: interface I[]();
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~
-interface I[]();
+interface I[T:! type](N:! T);
 
-// CHECK:STDOUT: --- fail_todo_generic.carbon
+// CHECK:STDOUT: --- generic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %N: T = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -23,7 +22,13 @@ interface I[]();
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .I = %I.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {
+// CHECK:STDOUT:     %T.loc11_13.1: type = param T
+// CHECK:STDOUT:     %T.loc11_13.2: type = bind_symbolic_name T 0, %T.loc11_13.1 [symbolic = constants.%T]
+// CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc11_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:     %N.loc11_23.1: T = param N
+// CHECK:STDOUT:     %N.loc11_23.2: T = bind_symbolic_name N 1, %N.loc11_23.1 [symbolic = constants.%N]
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I;

+ 29 - 6
toolchain/check/testdata/namespace/fail_params.carbon

@@ -23,11 +23,21 @@ fn A.F() {}
 // CHECK:STDERR:
 namespace B(n: i32);
 
-// CHECK:STDERR: fail_params.carbon:[[@LINE+3]]:12: ERROR: `namespace` declaration cannot have parameters.
+// CHECK:STDERR: fail_params.carbon:[[@LINE+4]]:12: ERROR: `namespace` declaration cannot have parameters.
 // CHECK:STDERR: namespace C[T:! type](x: T);
 // CHECK:STDERR:            ^~~~~~~~~~
+// CHECK:STDERR:
 namespace C[T:! type](x: T);
 
+namespace D;
+// CHECK:STDERR: fail_params.carbon:[[@LINE+6]]:4: ERROR: Redeclaration differs because of parameter list.
+// CHECK:STDERR: fn D(T:! type).F() {}
+// CHECK:STDERR:    ^
+// CHECK:STDERR: fail_params.carbon:[[@LINE-4]]:1: Previously declared without parameter list.
+// CHECK:STDERR: namespace D;
+// CHECK:STDERR: ^~~~~~~~~~~~
+fn D(T:! type).F() {}
+
 // CHECK:STDOUT: --- fail_params.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -37,6 +47,8 @@ namespace C[T:! type](x: T);
 // CHECK:STDOUT:   %Int32: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %struct.2: Int32 = struct_value () [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -45,6 +57,7 @@ namespace C[T:! type](x: T);
 // CHECK:STDOUT:     .A = %A
 // CHECK:STDOUT:     .B = %B
 // CHECK:STDOUT:     .C = %C
+// CHECK:STDOUT:     .D = %D
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
 // CHECK:STDOUT:   %A: <namespace> = namespace [template] {
@@ -58,12 +71,17 @@ namespace C[T:! type](x: T);
 // CHECK:STDOUT:   %n.loc24_13.1: i32 = param n
 // CHECK:STDOUT:   %n.loc24_13.2: i32 = bind_name n, %n.loc24_13.1
 // CHECK:STDOUT:   %B: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %T.loc29_13.1: type = param T
-// CHECK:STDOUT:   %T.loc29_13.2: type = bind_symbolic_name T 0, %T.loc29_13.1 [symbolic = constants.%T]
-// CHECK:STDOUT:   %T.ref: type = name_ref T, %T.loc29_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:   %x.loc29_23.1: T = param x
-// CHECK:STDOUT:   %x.loc29_23.2: T = bind_name x, %x.loc29_23.1
+// CHECK:STDOUT:   %T.loc30_13.1: type = param T
+// CHECK:STDOUT:   %T.loc30_13.2: type = bind_symbolic_name T 0, %T.loc30_13.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   %T.ref: type = name_ref T, %T.loc30_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:   %x.loc30_23.1: T = param x
+// CHECK:STDOUT:   %x.loc30_23.2: T = bind_name x, %x.loc30_23.1
 // CHECK:STDOUT:   %C: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %D: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {
+// CHECK:STDOUT:     %T.loc39_6.1: type = param T
+// CHECK:STDOUT:     %T.loc39_6.2: type = bind_symbolic_name T 0, %T.loc39_6.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
@@ -73,3 +91,8 @@ namespace C[T:! type](x: T);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 47 - 3
toolchain/check/testdata/packages/no_prelude/cross_package_import.carbon

@@ -162,10 +162,10 @@ library "reopen_other";
 
 import Other library "fn";
 
-// CHECK:STDERR: fail_main_reopen_other.carbon:[[@LINE+7]]:11: ERROR: Imported packages cannot be used for declarations.
+// CHECK:STDERR: fail_main_reopen_other.carbon:[[@LINE+7]]:1: ERROR: Duplicate name being declared in the same scope.
 // CHECK:STDERR: namespace Other;
-// CHECK:STDERR:           ^~~~~
-// CHECK:STDERR: fail_main_reopen_other.carbon:[[@LINE-5]]:1: Package imported here.
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_main_reopen_other.carbon:[[@LINE-5]]:1: Name is previously declared here.
 // CHECK:STDERR: import Other library "fn";
 // CHECK:STDERR: ^~~~~~
 // CHECK:STDERR:
@@ -174,6 +174,24 @@ namespace Other;
 // This is not diagnosed after the diagnostic on `namespace Other;`.
 fn Other.G() {}
 
+// --- fail_main_reopen_other_nested.carbon
+
+library "reopen_other_nested";
+
+import Other library "fn";
+
+// CHECK:STDERR: fail_main_reopen_other_nested.carbon:[[@LINE+7]]:11: ERROR: Imported packages cannot be used for declarations.
+// CHECK:STDERR: namespace Other.Nested;
+// CHECK:STDERR:           ^~~~~
+// CHECK:STDERR: fail_main_reopen_other_nested.carbon:[[@LINE-5]]:1: Package imported here.
+// CHECK:STDERR: import Other library "fn";
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+namespace Other.Nested;
+
+// This is not diagnosed after the diagnostic on `namespace Other;`.
+fn Other.Nested.F() {}
+
 // --- fail_main_add_to_other.carbon
 
 library "add_to_other";
@@ -475,6 +493,32 @@ fn UseF() { Other.F(); }
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_main_reopen_other_nested.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct: F = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Other = %Other
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Other: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Nested = %Nested
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Nested: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_main_add_to_other.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 4 - 0
toolchain/sem_ir/interface.h

@@ -32,6 +32,10 @@ struct Interface : public Printable<Interface> {
   NameId name_id;
   // The parent scope.
   NameScopeId parent_scope_id;
+  // A block containing a single reference instruction per implicit parameter.
+  InstBlockId implicit_param_refs_id;
+  // A block containing a single reference instruction per parameter.
+  InstBlockId param_refs_id;
   // The first declaration of the interface. This is a InterfaceDecl.
   InstId decl_id;