Quellcode durchsuchen

Initial support for empty named constraints (#6245)

Type check named constraint decls and definitions. We don't correctly
error if you put a `fn` inside them. There is no support for `require`
or `alias` yet, so there's nothing useful you can do with them yet.

We have attempted to share code between `interface` and `constraint` as
they are quite similar. First by splitting out some of
handle_interface.cpp to a separate file. Second by sharing some code
paths when you want a facet type from either one, as they both turn into
a facet type.
Dana Jansens vor 6 Monaten
Ursprung
Commit
22580a47d3
41 geänderte Dateien mit 1770 neuen und 141 gelöschten Zeilen
  1. 34 11
      toolchain/check/call.cpp
  2. 3 0
      toolchain/check/context.h
  3. 19 0
      toolchain/check/eval_inst.cpp
  4. 18 2
      toolchain/check/facet_type.cpp
  5. 14 3
      toolchain/check/facet_type.h
  6. 1 1
      toolchain/check/generic.cpp
  7. 1 1
      toolchain/check/generic.h
  8. 38 88
      toolchain/check/handle_interface.cpp
  9. 150 10
      toolchain/check/handle_named_constraint.cpp
  10. 131 0
      toolchain/check/interface.cpp
  11. 27 0
      toolchain/check/interface.h
  12. 2 2
      toolchain/check/merge.h
  13. 5 4
      toolchain/check/node_stack.h
  14. 97 0
      toolchain/check/testdata/named_constraint/basic.carbon
  15. 183 0
      toolchain/check/testdata/named_constraint/convert.carbon
  16. 191 0
      toolchain/check/testdata/named_constraint/empty.carbon
  17. 257 0
      toolchain/check/testdata/named_constraint/empty_generic.carbon
  18. 184 0
      toolchain/check/testdata/named_constraint/generic.carbon
  19. 74 0
      toolchain/check/testdata/named_constraint/invalid_members.carbon
  20. 17 0
      toolchain/check/type.cpp
  21. 13 0
      toolchain/check/type.h
  22. 2 1
      toolchain/check/type_completion.cpp
  23. 2 2
      toolchain/lower/file_context.cpp
  24. 2 0
      toolchain/parse/node_ids.h
  25. 27 10
      toolchain/parse/testdata/generics/named_constraint/basic.carbon
  26. 30 5
      toolchain/parse/testdata/generics/named_constraint/invalid_method.carbon
  27. 37 0
      toolchain/parse/testdata/generics/named_constraint/template_constraint.carbon
  28. 1 0
      toolchain/sem_ir/BUILD
  29. 2 0
      toolchain/sem_ir/expr_info.cpp
  30. 10 0
      toolchain/sem_ir/file.h
  31. 47 0
      toolchain/sem_ir/formatter.cpp
  32. 4 0
      toolchain/sem_ir/formatter.h
  33. 1 0
      toolchain/sem_ir/id_kind.h
  34. 7 0
      toolchain/sem_ir/ids.h
  35. 4 0
      toolchain/sem_ir/inst_fingerprinter.cpp
  36. 2 0
      toolchain/sem_ir/inst_kind.def
  37. 35 0
      toolchain/sem_ir/inst_namer.cpp
  38. 4 1
      toolchain/sem_ir/inst_namer.h
  39. 54 0
      toolchain/sem_ir/named_constraint.h
  40. 10 0
      toolchain/sem_ir/stringify.cpp
  41. 30 0
      toolchain/sem_ir/typed_insts.h

+ 34 - 11
toolchain/check/call.cpp

@@ -116,24 +116,42 @@ static auto PerformCallToGenericClass(Context& context, SemIR::LocId loc_id,
                                          .specific_id = *callee_specific_id});
 }
 
-// Performs a call where the callee is the name of a generic interface, such as
-// `AddWith(i32)`.
-static auto PerformCallToGenericInterface(
-    Context& context, SemIR::LocId loc_id, SemIR::InterfaceId interface_id,
+static auto EntityFromInterfaceOrNamedConstraint(
+    Context& context, SemIR::InterfaceId interface_id)
+    -> const SemIR::EntityWithParamsBase& {
+  return context.interfaces().Get(interface_id);
+}
+
+static auto EntityFromInterfaceOrNamedConstraint(
+    Context& context, SemIR::NamedConstraintId named_constraint_id)
+    -> const SemIR::EntityWithParamsBase& {
+  return context.named_constraints().Get(named_constraint_id);
+}
+
+// Performs a call where the callee is the name of a generic interface or named
+// constraint, such as `AddWith(i32)`.
+template <typename IdT>
+  requires SameAsOneOf<IdT, SemIR::InterfaceId, SemIR::NamedConstraintId>
+static auto PerformCallToGenericInterfaceOrNamedConstaint(
+    Context& context, SemIR::LocId loc_id, IdT id,
     SemIR::SpecificId enclosing_specific_id,
     llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
-  const auto& interface = context.interfaces().Get(interface_id);
+  const auto& entity = EntityFromInterfaceOrNamedConstraint(context, id);
   auto callee_specific_id =
-      ResolveCalleeInCall(context, loc_id, interface,
-                          EntityKind::GenericInterface, enclosing_specific_id,
+      ResolveCalleeInCall(context, loc_id, entity, EntityKind::GenericInterface,
+                          enclosing_specific_id,
                           /*self_type_id=*/SemIR::InstId::None,
                           /*self_id=*/SemIR::InstId::None, arg_ids);
   if (!callee_specific_id) {
     return SemIR::ErrorInst::InstId;
   }
-  return GetOrAddInst(
-      context, loc_id,
-      FacetTypeFromInterface(context, interface_id, *callee_specific_id));
+  std::optional<SemIR::FacetType> facet_type;
+  if constexpr (std::same_as<IdT, SemIR::InterfaceId>) {
+    facet_type = FacetTypeFromInterface(context, id, *callee_specific_id);
+  } else {
+    facet_type = FacetTypeFromNamedConstraint(context, id, *callee_specific_id);
+  }
+  return GetOrAddInst(context, loc_id, *facet_type);
 }
 
 // Builds an appropriate specific function for the callee, also handling
@@ -307,10 +325,15 @@ static auto PerformCallToNonFunction(Context& context, SemIR::LocId loc_id,
                                        arg_ids);
     }
     case CARBON_KIND(SemIR::GenericInterfaceType generic_interface): {
-      return PerformCallToGenericInterface(
+      return PerformCallToGenericInterfaceOrNamedConstaint(
           context, loc_id, generic_interface.interface_id,
           generic_interface.enclosing_specific_id, arg_ids);
     }
+    case CARBON_KIND(SemIR::GenericNamedConstraintType generic_constraint): {
+      return PerformCallToGenericInterfaceOrNamedConstaint(
+          context, loc_id, generic_constraint.named_constraint_id,
+          generic_constraint.enclosing_specific_id, arg_ids);
+    }
     default: {
       CARBON_DIAGNOSTIC(CallToNonCallable, Error,
                         "value of type {0} is not callable", TypeOfInstId);

+ 3 - 0
toolchain/check/context.h

@@ -265,6 +265,9 @@ class Context {
   auto classes() -> SemIR::ClassStore& { return sem_ir().classes(); }
   auto vtables() -> SemIR::VtableStore& { return sem_ir().vtables(); }
   auto interfaces() -> SemIR::InterfaceStore& { return sem_ir().interfaces(); }
+  auto named_constraints() -> SemIR::NamedConstraintStore& {
+    return sem_ir().named_constraints();
+  }
   auto associated_constants() -> SemIR::AssociatedConstantStore& {
     return sem_ir().associated_constants();
   }

+ 19 - 0
toolchain/check/eval_inst.cpp

@@ -451,6 +451,25 @@ auto EvalConstantInst(Context& context, SemIR::InterfaceDecl inst)
       context.generics().GetSelfSpecific(interface_info.generic_id)));
 }
 
+auto EvalConstantInst(Context& context, SemIR::NamedConstraintDecl inst)
+    -> ConstantEvalResult {
+  const auto& named_constraint_info =
+      context.named_constraints().Get(inst.named_constraint_id);
+
+  // If the named constraint has generic parameters, we don't produce a named
+  // constraint type, but a callable whose return value is a named constraint
+  // type.
+  if (named_constraint_info.has_parameters()) {
+    return ConstantEvalResult::NewSamePhase(SemIR::StructValue{
+        .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty});
+  }
+
+  // A non-parameterized named constraint declaration evaluates to a facet type.
+  return ConstantEvalResult::NewAnyPhase(FacetTypeFromNamedConstraint(
+      context, inst.named_constraint_id,
+      context.generics().GetSelfSpecific(named_constraint_info.generic_id)));
+}
+
 auto EvalConstantInst(Context& context, SemIR::NameRef inst)
     -> ConstantEvalResult {
   // A name reference evaluates to the value the name resolves to.

+ 18 - 2
toolchain/check/facet_type.cpp

@@ -8,6 +8,7 @@
 
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
+#include "toolchain/base/kind_switch.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/generic.h"
@@ -24,8 +25,23 @@ namespace Carbon::Check {
 
 auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
                             SemIR::SpecificId specific_id) -> SemIR::FacetType {
-  auto info =
-      SemIR::FacetTypeInfo{.extend_constraints = {{interface_id, specific_id}}};
+  auto info = SemIR::FacetTypeInfo{};
+
+  info.extend_constraints.push_back({interface_id, specific_id});
+  // TODO: Add `require impls` to the set of constraints.
+
+  info.Canonicalize();
+  SemIR::FacetTypeId facet_type_id = context.facet_types().Add(info);
+  return {.type_id = SemIR::TypeType::TypeId, .facet_type_id = facet_type_id};
+}
+
+auto FacetTypeFromNamedConstraint(
+    Context& context, SemIR::NamedConstraintId /*named_constraint_id*/,
+    SemIR::SpecificId /*specific_id*/) -> SemIR::FacetType {
+  auto info = SemIR::FacetTypeInfo{};
+
+  // TODO: Add `require impls` to the set of constraints.
+
   info.Canonicalize();
   SemIR::FacetTypeId facet_type_id = context.facet_types().Add(info);
   return {.type_id = SemIR::TypeType::TypeId, .facet_type_id = facet_type_id};

+ 14 - 3
toolchain/check/facet_type.h

@@ -8,16 +8,27 @@
 #include <compare>
 
 #include "toolchain/check/context.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
 
-// Create a FacetType typed instruction object consisting of a single
-// interface. The `specific_id` specifies arguments in the case the interface is
-// generic.
+// Create a FacetType typed instruction object consisting of a interface. The
+// `specific_id` specifies arguments in the case the interface is generic.
+//
+// The resulting FacetType may contain multiple interfaces if the named
+// interface contains `require` declarations.
 auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
                             SemIR::SpecificId specific_id) -> SemIR::FacetType;
 
+// Create a FacetType typed instruction object consisting of a named constraint.
+// The `specific_id` specifies arguments in the case the named constraint is
+// generic.
+auto FacetTypeFromNamedConstraint(Context& context,
+                                  SemIR::NamedConstraintId named_constraint_id,
+                                  SemIR::SpecificId specific_id)
+    -> SemIR::FacetType;
+
 // Given an ImplWitnessAccessSubstituted, returns the InstId of the
 // ImplWitnessAccess. Otherwise, returns the input `inst_id` unchanged.
 //

+ 1 - 1
toolchain/check/generic.cpp

@@ -730,7 +730,7 @@ auto ResolveSpecificDefinition(Context& context, SemIR::LocId loc_id,
 }
 
 auto DiagnoseIfGenericMissingExplicitParameters(
-    Context& context, SemIR::EntityWithParamsBase& entity_base) -> void {
+    Context& context, const SemIR::EntityWithParamsBase& entity_base) -> void {
   if (!entity_base.implicit_param_patterns_id.has_value() ||
       entity_base.param_patterns_id.has_value()) {
     return;

+ 1 - 1
toolchain/check/generic.h

@@ -123,7 +123,7 @@ auto ResolveSpecificDefinition(Context& context, SemIR::LocId loc_id,
 // Diagnoses if an entity has implicit parameters, indicating it's generic, but
 // is missing explicit parameters.
 auto DiagnoseIfGenericMissingExplicitParameters(
-    Context& context, SemIR::EntityWithParamsBase& entity_base) -> void;
+    Context& context, const SemIR::EntityWithParamsBase& entity_base) -> void;
 
 }  // namespace Carbon::Check
 

+ 38 - 88
toolchain/check/handle_interface.cpp

@@ -10,11 +10,15 @@
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/interface.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/interface.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -26,11 +30,12 @@ auto HandleParseNode(Context& context, Parse::InterfaceIntroducerId node_id)
   // Create an instruction block to hold the instructions created as part of the
   // interface signature, such as generic parameters.
   context.inst_block_stack().Push();
-  // Push the bracketing node.
-  context.node_stack().Push(node_id);
   // Optional modifiers and the name follow.
   context.decl_introducer_state_stack().Push<Lex::TokenKind::Interface>();
   context.decl_name_stack().PushScopeAndStartName();
+
+  // Push the bracketing node.
+  context.node_stack().Push(node_id);
   return true;
 }
 
@@ -56,87 +61,44 @@ static auto BuildInterfaceDecl(Context& context,
   // Add the interface declaration.
   auto interface_decl = SemIR::InterfaceDecl{
       SemIR::TypeType::TypeId, SemIR::InterfaceId::None, decl_block_id};
-  auto interface_decl_id = AddPlaceholderInst(context, node_id, interface_decl);
+  auto decl_inst_id = AddPlaceholderInst(context, node_id, interface_decl);
 
   SemIR::Interface interface_info = {name_context.MakeEntityWithParamsBase(
-      name, interface_decl_id, /*is_extern=*/false,
-      SemIR::LibraryNameId::None)};
+      name, decl_inst_id, /*is_extern=*/false, SemIR::LibraryNameId::None)};
 
   DiagnoseIfGenericMissingExplicitParameters(context, interface_info);
 
   // Check whether this is a redeclaration.
   SemIR::ScopeLookupResult lookup_result =
       context.decl_name_stack().LookupOrAddName(
-          name_context, interface_decl_id,
-          introducer.modifier_set.GetAccessKind());
-  if (lookup_result.is_poisoned()) {
-    // This is a declaration of a poisoned name.
-    DiagnosePoisonedName(context, name_context.name_id_for_new_inst(),
-                         lookup_result.poisoning_loc_id(), name_context.loc_id);
-  } else if (lookup_result.is_found()) {
-    SemIR::InstId existing_id = lookup_result.target_inst_id();
-    if (auto existing_interface_decl =
-            context.insts().Get(existing_id).TryAs<SemIR::InterfaceDecl>()) {
-      auto existing_interface =
-          context.interfaces().Get(existing_interface_decl->interface_id);
-      if (CheckRedeclParamsMatch(
-              context,
-              DeclParams(SemIR::LocId(interface_decl_id),
-                         name.first_param_node_id, name.last_param_node_id,
-                         name.implicit_param_patterns_id,
-                         name.param_patterns_id),
-              DeclParams(existing_interface))) {
-        // TODO: This should be refactored a little, particularly for
-        // prev_import_ir_id. See similar logic for classes and functions, which
-        // might also be refactored to merge.
-        DiagnoseIfInvalidRedecl(
-            context, Lex::TokenKind::Interface, existing_interface.name_id,
-            RedeclInfo(interface_info, node_id, is_definition),
-            RedeclInfo(existing_interface,
-                       SemIR::LocId(existing_interface.latest_decl_id()),
-                       existing_interface.has_definition_started()),
-            /*prev_import_ir_id=*/SemIR::ImportIRId::None);
-
-        // Can't merge interface definitions due to the generic requirements.
-        if (!is_definition || !existing_interface.has_definition_started()) {
-          // This is a redeclaration of an existing interface.
-          interface_decl.interface_id = existing_interface_decl->interface_id;
-          interface_decl.type_id = existing_interface_decl->type_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.
-      DiagnoseDuplicateName(context, name_context.name_id, name_context.loc_id,
-                            SemIR::LocId(existing_id));
-    }
-  }
+          name_context, decl_inst_id, introducer.modifier_set.GetAccessKind());
+  if (auto existing_decl = TryGetExistingDecl(context, name, lookup_result,
+                                              interface_info, is_definition)) {
+    auto existing_interface_decl = existing_decl->As<SemIR::InterfaceDecl>();
+    interface_decl.interface_id = existing_interface_decl.interface_id;
+    interface_decl.type_id = existing_interface_decl.type_id;
+    // TODO: If the new declaration is a definition, keep its parameter
+    // and implicit parameter lists rather than the ones from the
+    // previous declaration.
 
-  // Create a new interface if this isn't a valid redeclaration.
-  if (!interface_decl.interface_id.has_value()) {
-    // TODO: If this is an invalid redeclaration of a non-interface entity or
-    // there was an error in the qualifier, we will have lost track of the
-    // interface name here. We should keep track of it even if the name is
-    // invalid.
-    interface_info.generic_id = BuildGenericDecl(context, interface_decl_id);
+    auto prev_decl_generic_id =
+        context.interfaces().Get(interface_decl.interface_id).generic_id;
+    FinishGenericRedecl(context, prev_decl_generic_id);
+  } else {
+    // Create a new interface if this isn't a valid redeclaration.
+    interface_info.generic_id = BuildGenericDecl(context, decl_inst_id);
     interface_decl.interface_id = context.interfaces().Add(interface_info);
     if (interface_info.has_parameters()) {
       interface_decl.type_id =
           GetGenericInterfaceType(context, interface_decl.interface_id,
                                   context.scope_stack().PeekSpecificId());
     }
-  } else {
-    auto prev_decl_generic_id =
-        context.interfaces().Get(interface_decl.interface_id).generic_id;
-    FinishGenericRedecl(context, prev_decl_generic_id);
   }
 
   // Write the interface ID into the InterfaceDecl.
-  ReplaceInstBeforeConstantUse(context, interface_decl_id, interface_decl);
+  ReplaceInstBeforeConstantUse(context, decl_inst_id, interface_decl);
 
-  return {interface_decl.interface_id, interface_decl_id};
+  return {interface_decl.interface_id, decl_inst_id};
 }
 
 auto HandleParseNode(Context& context, Parse::InterfaceDeclId node_id) -> bool {
@@ -147,16 +109,16 @@ auto HandleParseNode(Context& context, Parse::InterfaceDeclId node_id) -> bool {
 
 auto HandleParseNode(Context& context,
                      Parse::InterfaceDefinitionStartId node_id) -> bool {
-  auto [interface_id, interface_decl_id] =
+  auto [interface_id, decl_inst_id] =
       BuildInterfaceDecl(context, node_id, /*is_definition=*/true);
   auto& interface_info = context.interfaces().Get(interface_id);
 
   // Track that this declaration is the definition.
   CARBON_CHECK(!interface_info.has_definition_started(),
                "Can't merge with defined interfaces.");
-  interface_info.definition_id = interface_decl_id;
+  interface_info.definition_id = decl_inst_id;
   interface_info.scope_id = context.name_scopes().Add(
-      interface_decl_id, SemIR::NameId::None, interface_info.parent_scope_id);
+      decl_inst_id, SemIR::NameId::None, interface_info.parent_scope_id);
   context.name_scopes()
       .Get(interface_info.scope_id)
       .set_is_interface_definition();
@@ -167,35 +129,21 @@ auto HandleParseNode(Context& context,
   StartGenericDefinition(context, interface_info.generic_id);
 
   context.inst_block_stack().Push();
-  context.node_stack().Push(node_id, interface_id);
 
   // We use the arg stack to build the witness table type.
   context.args_type_info_stack().Push();
 
-  // Declare and introduce `Self`.
+  // Declare and introduce `Self`. We model `Self` as a symbolic binding whose
+  // type is the interface, excluding any other interfaces mentioned by
+  // `require` declarations.
   SemIR::TypeId self_type_id =
       GetInterfaceType(context, interface_id, self_specific_id);
-
-  // We model `Self` as a symbolic binding whose type is the interface.
-  // Because there is no equivalent non-symbolic value, we use `None` as
-  // the `value_id` on the `BindSymbolicName`.
-  auto entity_name_id = context.entity_names().AddSymbolicBindingName(
-      SemIR::NameId::SelfType, interface_info.scope_id,
-      context.scope_stack().AddCompileTimeBinding(),
-      /*is_template=*/false);
-  interface_info.self_param_id =
-      AddInst(context, SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
-                           {.type_id = self_type_id,
-                            .entity_name_id = entity_name_id,
-                            .value_id = SemIR::InstId::None}));
-  context.scope_stack().PushCompileTimeBinding(interface_info.self_param_id);
-  context.name_scopes().AddRequiredName(interface_info.scope_id,
-                                        SemIR::NameId::SelfType,
-                                        interface_info.self_param_id);
+  interface_info.self_param_id = AddSelfGenericParameter(
+      context, self_type_id, interface_info.scope_id, /*is_template=*/false);
 
   // Enter the interface scope.
-  context.scope_stack().PushForEntity(
-      interface_decl_id, interface_info.scope_id, self_specific_id);
+  context.scope_stack().PushForEntity(decl_inst_id, interface_info.scope_id,
+                                      self_specific_id);
 
   // TODO: Handle the case where there's control flow in the interface body. For
   // example:
@@ -207,6 +155,8 @@ auto HandleParseNode(Context& context,
   // We may need to track a list of instruction blocks here, as we do for a
   // function.
   interface_info.body_block_id = context.inst_block_stack().PeekOrAdd();
+
+  context.node_stack().Push(node_id, interface_id);
   return true;
 }
 

+ 150 - 10
toolchain/check/handle_named_constraint.cpp

@@ -3,29 +3,169 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
+#include "toolchain/check/interface.h"
+#include "toolchain/check/modifiers.h"
+#include "toolchain/check/type.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/named_constraint.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
-auto HandleParseNode(Context& context, Parse::NamedConstraintDeclId node_id)
-    -> bool {
-  return context.TODO(node_id, "HandleNamedConstraintDecl");
+auto HandleParseNode(Context& context,
+                     Parse::NamedConstraintIntroducerId node_id) -> bool {
+  // This named constraint is potentially generic.
+  StartGenericDecl(context);
+  // Create an instruction block to hold the instructions created as part of the
+  // named constraint signature, such as generic parameters.
+  context.inst_block_stack().Push();
+  // Optional modifiers and the name follow.
+  context.decl_introducer_state_stack().Push<Lex::TokenKind::Constraint>();
+  context.decl_name_stack().PushScopeAndStartName();
+
+  // Push the bracketing node.
+  context.node_stack().Push(node_id);
+  return true;
 }
 
-auto HandleParseNode(Context& context,
-                     Parse::NamedConstraintDefinitionId node_id) -> bool {
-  // Note that the decl_name_stack will be popped by `ProcessNodeIds`.
-  return context.TODO(node_id, "HandleNamedConstraintDefinition");
+static auto BuildNamedConstraintDecl(Context& context,
+                                     Parse::AnyNamedConstraintDeclId node_id,
+                                     bool is_definition)
+    -> std::tuple<SemIR::NamedConstraintId, SemIR::InstId> {
+  auto name = PopNameComponent(context);
+  auto name_context = context.decl_name_stack().FinishName(name);
+  context.node_stack()
+      .PopAndDiscardSoloNodeId<Parse::NodeKind::NamedConstraintIntroducer>();
+
+  // TODO: PopSoloNodeId(`template`) if it's present, and track that in the
+  // NamedConstraint. Or maybe it should be a modifier, like `abstract class`?
+
+  // Process modifiers.
+  auto [_, parent_scope_inst] =
+      context.name_scopes().GetInstIfValid(name_context.parent_scope_id);
+  auto introducer =
+      context.decl_introducer_state_stack().Pop<Lex::TokenKind::Constraint>();
+  CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst);
+  LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Access);
+
+  auto decl_block_id = context.inst_block_stack().Pop();
+
+  // Add the constraint declaration.
+  auto constraint_decl = SemIR::NamedConstraintDecl{
+      SemIR::TypeType::TypeId, SemIR::NamedConstraintId::None, decl_block_id};
+  auto decl_inst_id = AddPlaceholderInst(context, node_id, constraint_decl);
+
+  SemIR::NamedConstraint constraint_info = {
+      name_context.MakeEntityWithParamsBase(name, decl_inst_id,
+                                            /*is_extern=*/false,
+                                            SemIR::LibraryNameId::None)};
+
+  DiagnoseIfGenericMissingExplicitParameters(context, constraint_info);
+
+  // Check whether this is a redeclaration.
+  SemIR::ScopeLookupResult lookup_result =
+      context.decl_name_stack().LookupOrAddName(
+          name_context, decl_inst_id, introducer.modifier_set.GetAccessKind());
+  if (auto existing_decl = TryGetExistingDecl(context, name, lookup_result,
+                                              constraint_info, is_definition)) {
+    auto existing_constraint_decl =
+        existing_decl->As<SemIR::NamedConstraintDecl>();
+    constraint_decl.named_constraint_id =
+        existing_constraint_decl.named_constraint_id;
+    constraint_decl.type_id = existing_constraint_decl.type_id;
+    // TODO: If the new declaration is a definition, keep its parameter
+    // and implicit parameter lists rather than the ones from the
+    // previous declaration.
+
+    auto prev_decl_generic_id = context.named_constraints()
+                                    .Get(constraint_decl.named_constraint_id)
+                                    .generic_id;
+    FinishGenericRedecl(context, prev_decl_generic_id);
+  } else {
+    // Create a new named constraint if this isn't a valid redeclaration.
+    constraint_info.generic_id = BuildGenericDecl(context, decl_inst_id);
+    constraint_decl.named_constraint_id =
+        context.named_constraints().Add(constraint_info);
+    if (constraint_info.has_parameters()) {
+      constraint_decl.type_id = GetGenericNamedConstraintType(
+          context, constraint_decl.named_constraint_id,
+          context.scope_stack().PeekSpecificId());
+    }
+  }
+
+  // Write the completed NamedConstraintDecl instruction.
+  ReplaceInstBeforeConstantUse(context, decl_inst_id, constraint_decl);
+
+  return {constraint_decl.named_constraint_id, decl_inst_id};
+}
+
+auto HandleParseNode(Context& context, Parse::NamedConstraintDeclId node_id)
+    -> bool {
+  BuildNamedConstraintDecl(context, node_id, /*is_definition=*/false);
+  context.decl_name_stack().PopScope();
+  return true;
 }
 
 auto HandleParseNode(Context& context,
                      Parse::NamedConstraintDefinitionStartId node_id) -> bool {
-  return context.TODO(node_id, "HandleNamedConstraintDefinitionStart");
+  auto [named_constraint_id, decl_inst_id] =
+      BuildNamedConstraintDecl(context, node_id, /*is_definition=*/true);
+  auto& constraint_info = context.named_constraints().Get(named_constraint_id);
+
+  // TODO: Support for `template constraint`.
+  bool is_template = false;
+
+  // Track that this declaration is the definition.
+  CARBON_CHECK(!constraint_info.has_definition_started(),
+               "Can't merge with defined named constraints.");
+  constraint_info.definition_id = decl_inst_id;
+  constraint_info.scope_id = context.name_scopes().Add(
+      decl_inst_id, SemIR::NameId::None, constraint_info.parent_scope_id);
+
+  auto self_specific_id =
+      context.generics().GetSelfSpecific(constraint_info.generic_id);
+  StartGenericDefinition(context, constraint_info.generic_id);
+
+  context.inst_block_stack().Push();
+
+  // Declare and introduce `Self`. We model `Self` as a symbolic binding whose
+  // type is the named constraint, excluding any other interfaces mentioned by
+  // `require` declarations. This makes it an empty facet type.
+  SemIR::TypeId self_type_id =
+      GetNamedConstraintType(context, named_constraint_id, self_specific_id);
+  constraint_info.self_param_id = AddSelfGenericParameter(
+      context, self_type_id, constraint_info.scope_id, is_template);
+
+  // Enter the constraint scope.
+  context.scope_stack().PushForEntity(decl_inst_id, constraint_info.scope_id,
+                                      self_specific_id);
+
+  constraint_info.body_block_id = context.inst_block_stack().PeekOrAdd();
+
+  context.node_stack().Push(node_id, named_constraint_id);
+  return true;
 }
 
 auto HandleParseNode(Context& context,
-                     Parse::NamedConstraintIntroducerId node_id) -> bool {
-  return context.TODO(node_id, "HandleNamedConstraintIntroducer");
+                     Parse::NamedConstraintDefinitionId /*node_id*/) -> bool {
+  auto named_constraint_id =
+      context.node_stack()
+          .Pop<Parse::NodeKind::NamedConstraintDefinitionStart>();
+  context.inst_block_stack().Pop();
+
+  const auto& constraint_info =
+      context.named_constraints().Get(named_constraint_id);
+
+  // TODO: Do something with `require` and `alias` statements in the body of the
+  // constraint.
+
+  FinishGenericDefinition(context, constraint_info.generic_id);
+
+  // The decl_name_stack and scopes are popped by `ProcessNodeIds`.
+  return true;
 }
 
 }  // namespace Carbon::Check

+ 131 - 0
toolchain/check/interface.cpp

@@ -7,11 +7,15 @@
 #include <algorithm>
 #include <cstddef>
 
+#include "common/concepts.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/merge.h"
+#include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -219,4 +223,131 @@ auto GetTypeForSpecificAssociatedEntity(Context& context, SemIR::LocId loc_id,
   CARBON_FATAL("Unexpected kind for associated constant {0}", decl);
 }
 
+auto AddSelfGenericParameter(Context& context, SemIR::TypeId type_id,
+                             SemIR::NameScopeId scope_id, bool is_template)
+    -> SemIR::InstId {
+  auto entity_name_id = context.entity_names().AddSymbolicBindingName(
+      SemIR::NameId::SelfType, scope_id,
+      context.scope_stack().AddCompileTimeBinding(), is_template);
+  // Because there is no equivalent non-symbolic value, we use `None` as
+  // the `value_id` on the `BindSymbolicName`.
+  auto self_param_inst_id =
+      AddInst(context, SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
+                           {.type_id = type_id,
+                            .entity_name_id = entity_name_id,
+                            .value_id = SemIR::InstId::None}));
+  context.scope_stack().PushCompileTimeBinding(self_param_inst_id);
+  context.name_scopes().AddRequiredName(scope_id, SemIR::NameId::SelfType,
+                                        self_param_inst_id);
+  return self_param_inst_id;
+}
+
+template <typename EntityT>
+  requires std::same_as<EntityT, SemIR::Interface>
+static auto TryGetEntity(Context& context, SemIR::Inst inst)
+    -> const SemIR::EntityWithParamsBase* {
+  if (auto decl = inst.TryAs<SemIR::InterfaceDecl>()) {
+    return &context.interfaces().Get(decl->interface_id);
+  } else {
+    return nullptr;
+  }
+}
+
+template <typename EntityT>
+  requires std::same_as<EntityT, SemIR::NamedConstraint>
+static auto TryGetEntity(Context& context, SemIR::Inst inst)
+    -> const SemIR::EntityWithParamsBase* {
+  if (auto decl = inst.TryAs<SemIR::NamedConstraintDecl>()) {
+    return &context.named_constraints().Get(decl->named_constraint_id);
+  } else {
+    return nullptr;
+  }
+}
+
+template <typename EntityT>
+  requires std::same_as<EntityT, SemIR::Interface>
+static constexpr auto DeclTokenKind() -> Lex::TokenKind {
+  return Lex::TokenKind::Interface;
+}
+
+template <typename EntityT>
+  requires std::same_as<EntityT, SemIR::NamedConstraint>
+static constexpr auto DeclTokenKind() -> Lex::TokenKind {
+  return Lex::TokenKind::Constraint;
+}
+
+template <typename EntityT>
+  requires SameAsOneOf<EntityT, SemIR::Interface, SemIR::NamedConstraint>
+auto TryGetExistingDecl(Context& context, const NameComponent& name,
+                        SemIR::ScopeLookupResult lookup_result,
+                        const EntityT& entity, bool is_definition)
+    -> std::optional<SemIR::Inst> {
+  if (lookup_result.is_poisoned()) {
+    // This is a declaration of a poisoned name.
+    DiagnosePoisonedName(context, name.name_id,
+                         lookup_result.poisoning_loc_id(), name.name_loc_id);
+    return std::nullopt;
+  }
+
+  if (!lookup_result.is_found()) {
+    return std::nullopt;
+  }
+
+  SemIR::InstId existing_id = lookup_result.target_inst_id();
+  SemIR::Inst existing_decl_inst = context.insts().Get(existing_id);
+  const auto* existing_decl_entity =
+      TryGetEntity<EntityT>(context, existing_decl_inst);
+  if (!existing_decl_entity) {
+    // This is a redeclaration with a different entity kind.
+    DiagnoseDuplicateName(context, name.name_id, name.name_loc_id,
+                          SemIR::LocId(existing_id));
+    return std::nullopt;
+  }
+
+  if (!CheckRedeclParamsMatch(
+          context,
+          DeclParams(SemIR::LocId(entity.latest_decl_id()),
+                     name.first_param_node_id, name.last_param_node_id,
+                     name.implicit_param_patterns_id, name.param_patterns_id),
+          DeclParams(*existing_decl_entity))) {
+    // Mismatch is diagnosed already if found.
+    return std::nullopt;
+  }
+
+  // TODO: This should be refactored a little, particularly for
+  // prev_import_ir_id. See similar logic for classes and functions, which
+  // might also be refactored to merge.
+  DiagnoseIfInvalidRedecl(
+      context, DeclTokenKind<EntityT>(), existing_decl_entity->name_id,
+      RedeclInfo(entity, SemIR::LocId(entity.latest_decl_id()), is_definition),
+      RedeclInfo(*existing_decl_entity,
+                 SemIR::LocId(existing_decl_entity->latest_decl_id()),
+                 existing_decl_entity->has_definition_started()),
+      /*prev_import_ir_id=*/SemIR::ImportIRId::None);
+
+  if (is_definition && existing_decl_entity->has_definition_started()) {
+    // DiagnoseIfInvalidRedecl would diagnose an error in this case, since we'd
+    // have two definitions. Given the declaration parts of the definitions
+    // match, we would be able to use the prior declaration for error recovery,
+    // except that having two definitions causes larger problems for generics.
+    // All interfaces (and named constraints) are generic with an implicit Self
+    // compile time binding.
+    return std::nullopt;
+  }
+
+  // This is a matching redeclaration of an existing entity of the same type.
+  return existing_decl_inst;
+}
+
+template auto TryGetExistingDecl(Context& context, const NameComponent& name,
+                                 SemIR::ScopeLookupResult lookup_result,
+                                 const SemIR::Interface& entity,
+                                 bool is_definition)
+    -> std::optional<SemIR::Inst>;
+template auto TryGetExistingDecl(Context& context, const NameComponent& name,
+                                 SemIR::ScopeLookupResult lookup_result,
+                                 const SemIR::NamedConstraint& entity,
+                                 bool is_definition)
+    -> std::optional<SemIR::Inst>;
+
 }  // namespace Carbon::Check

+ 27 - 0
toolchain/check/interface.h

@@ -5,8 +5,16 @@
 #ifndef CARBON_TOOLCHAIN_CHECK_INTERFACE_H_
 #define CARBON_TOOLCHAIN_CHECK_INTERFACE_H_
 
+#include <optional>
+
 #include "toolchain/check/context.h"
+#include "toolchain/check/decl_name_stack.h"
+#include "toolchain/check/name_component.h"
+#include "toolchain/parse/node_ids.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/name_scope.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
@@ -34,6 +42,25 @@ auto GetTypeForSpecificAssociatedEntity(Context& context, SemIR::LocId loc_id,
                                         SemIR::InstId self_witness_id)
     -> SemIR::TypeId;
 
+// Creates a symbolic binding for `Self` of type `type_id` in the scope of
+// `scope_id`, and add the name `Self` for the compile time binding.
+//
+// Returns the symbolic binding instruction.
+auto AddSelfGenericParameter(Context& context, SemIR::TypeId type_id,
+                             SemIR::NameScopeId scope_id, bool is_template)
+    -> SemIR::InstId;
+
+// Given a search result `lookup_result` for `name`, returns the previous valid
+// declaration of `name` if there is one. The `entity` is a new decl of the same
+// `name`, and the existing decl need to be of the same entity type. Otherwise,
+// produces diagnostics if needed and returns nullopt.
+template <typename EntityT>
+  requires SameAsOneOf<EntityT, SemIR::Interface, SemIR::NamedConstraint>
+auto TryGetExistingDecl(Context& context, const NameComponent& name,
+                        SemIR::ScopeLookupResult lookup_result,
+                        const EntityT& entity, bool is_definition)
+    -> std::optional<SemIR::Inst>;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_INTERFACE_H_

+ 2 - 2
toolchain/check/merge.h

@@ -18,8 +18,8 @@ auto DiagnoseExternRequiresDeclInApiFile(Context& context, SemIR::LocId loc_id)
 
 // Information on new and previous declarations for DiagnoseIfInvalidRedecl.
 struct RedeclInfo {
-  explicit RedeclInfo(SemIR::EntityWithParamsBase params, SemIR::LocId loc_id,
-                      bool is_definition)
+  explicit RedeclInfo(const SemIR::EntityWithParamsBase& params,
+                      SemIR::LocId loc_id, bool is_definition)
       : loc_id(loc_id),
         is_definition(is_definition),
         is_extern(params.is_extern),

+ 5 - 4
toolchain/check/node_stack.h

@@ -436,12 +436,16 @@ class NodeStack {
         return Id::KindFor<SemIR::InterfaceId>();
       case Parse::NodeKind::ImplDefinitionStart:
         return Id::KindFor<SemIR::ImplId>();
+      case Parse::NodeKind::NamedConstraintDefinitionStart:
+        return Id::KindFor<SemIR::NamedConstraintId>();
       case Parse::NodeKind::SelfTypeName:
       case Parse::NodeKind::SelfValueName:
         return Id::KindFor<SemIR::NameId>();
       case Parse::NodeKind::DefaultLibrary:
       case Parse::NodeKind::LibraryName:
         return Id::KindFor<SemIR::LibraryNameId>();
+      case Parse::NodeKind::AssociatedConstantInitializer:
+      case Parse::NodeKind::AssociatedConstantIntroducer:
       case Parse::NodeKind::BuiltinName:
       case Parse::NodeKind::ChoiceIntroducer:
       case Parse::NodeKind::ClassIntroducer:
@@ -457,8 +461,7 @@ class NodeStack {
       case Parse::NodeKind::InterfaceIntroducer:
       case Parse::NodeKind::LetInitializer:
       case Parse::NodeKind::LetIntroducer:
-      case Parse::NodeKind::AssociatedConstantIntroducer:
-      case Parse::NodeKind::AssociatedConstantInitializer:
+      case Parse::NodeKind::NamedConstraintIntroducer:
       case Parse::NodeKind::ReturnStatementStart:
       case Parse::NodeKind::StructLiteralStart:
       case Parse::NodeKind::StructTypeLiteralField:
@@ -515,8 +518,6 @@ class NodeStack {
       case Parse::NodeKind::MatchDefaultStart:
       case Parse::NodeKind::MatchIntroducer:
       case Parse::NodeKind::MatchStatementStart:
-      case Parse::NodeKind::NamedConstraintDefinitionStart:
-      case Parse::NodeKind::NamedConstraintIntroducer:
       case Parse::NodeKind::NamespaceStart:
       case Parse::NodeKind::PackageIntroducer:
       case Parse::NodeKind::ParenExprStart:

+ 97 - 0
toolchain/check/testdata/named_constraint/basic.carbon

@@ -0,0 +1,97 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/basic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/basic.carbon
+
+// --- definition.carbon
+library "[[@TEST_NAME]]";
+//@include-in-dumps
+
+constraint Declared {}
+
+// --- forward_declaration.carbon
+library "[[@TEST_NAME]]";
+//@include-in-dumps
+
+constraint ForwardDeclared;
+
+constraint ForwardDeclared {}
+
+// --- fail_name_conflict.carbon
+library "[[@TEST_NAME]]";
+
+class ForwardDeclared;
+
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE+7]]:12: error: duplicate name `ForwardDeclared` being declared in the same scope [NameDeclDuplicate]
+// CHECK:STDERR: constraint ForwardDeclared {}
+// CHECK:STDERR:            ^~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE-5]]:1: note: name is previously declared here [NameDeclPrevious]
+// CHECK:STDERR: class ForwardDeclared;
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+constraint ForwardDeclared {}
+
+// --- fail_defined_twice.carbon
+library "[[@TEST_NAME]]";
+
+constraint Declared {}
+
+// CHECK:STDERR: fail_defined_twice.carbon:[[@LINE+7]]:1: error: redefinition of `constraint Declared` [RedeclRedef]
+// CHECK:STDERR: constraint Declared {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_defined_twice.carbon:[[@LINE-5]]:1: note: previously defined here [RedeclPrevDef]
+// CHECK:STDERR: constraint Declared {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+constraint Declared {}
+
+// CHECK:STDOUT: --- definition.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %Self: %type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Declared = %Declared.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Declared.decl: type = constraint_decl @Declared [concrete = constants.%type] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: constraint @Declared {
+// CHECK:STDOUT:   %Self: %type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- forward_declaration.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %Self: %type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .ForwardDeclared = %ForwardDeclared.decl.loc4
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ForwardDeclared.decl.loc4: type = constraint_decl @ForwardDeclared [concrete = constants.%type] {} {}
+// CHECK:STDOUT:   %ForwardDeclared.decl.loc6: type = constraint_decl @ForwardDeclared [concrete = constants.%type] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: constraint @ForwardDeclared {
+// CHECK:STDOUT:   %Self: %type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 183 - 0
toolchain/check/testdata/named_constraint/convert.carbon

@@ -0,0 +1,183 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/convert.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/convert.carbon
+
+// --- type_and_with_named_constraint_self.carbon
+library "[[@TEST_NAME]]";
+
+constraint E {}
+
+fn F(T:! E) {}
+
+//@dump-sem-ir-begin
+fn G(T:! E & E) {
+  F(T);
+}
+//@dump-sem-ir-end
+
+// --- compatible_constraints.carbon
+library "[[@TEST_NAME]]";
+
+constraint E1 {}
+constraint E2 {}
+
+fn F(T:! E1) {}
+
+//@dump-sem-ir-begin
+fn G(T:! E2) {
+  F(T);
+}
+//@dump-sem-ir-end
+
+// --- fail_todo_unspecified.carbon
+library "[[@TEST_NAME]]";
+
+interface Z {}
+
+constraint E {
+  // CHECK:STDERR: fail_todo_unspecified.carbon:[[@LINE+8]]:3: error: unrecognized declaration introducer [UnrecognizedDecl]
+  // CHECK:STDERR:   require impls Z;
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_unspecified.carbon:[[@LINE+4]]:3: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR:   require impls Z;
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  require impls Z;
+}
+
+fn F(T:! E) {}
+
+class C {}
+impl C as Z {}
+
+fn G(T:! Z) {
+  // Anything that impls Z also impls E, since that's all it requires. Nothing
+  // needs to specify that T impls E.
+  F(T);
+  // Same for concrete types.
+  F(C);
+}
+
+// CHECK:STDOUT: --- type_and_with_named_constraint_self.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %T: %type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.e25: type = pattern_type %type [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %BitAndWith.type.8a6: type = facet_type <@BitAndWith, @BitAndWith(type)> [concrete]
+// CHECK:STDOUT:   %BitAndWith.Op.type.9a3: type = fn_type @BitAndWith.Op, @BitAndWith(type) [concrete]
+// CHECK:STDOUT:   %BitAndWith.impl_witness: <witness> = impl_witness imports.%BitAndWith.impl_witness_table [concrete]
+// CHECK:STDOUT:   %BitAndWith.facet: %BitAndWith.type.8a6 = facet_value type, (%BitAndWith.impl_witness) [concrete]
+// CHECK:STDOUT:   %.fa7: type = fn_type_with_self_type %BitAndWith.Op.type.9a3, %BitAndWith.facet [concrete]
+// CHECK:STDOUT:   %type.as.BitAndWith.impl.Op.type: type = fn_type @type.as.BitAndWith.impl.Op [concrete]
+// CHECK:STDOUT:   %type.as.BitAndWith.impl.Op: %type.as.BitAndWith.impl.Op.type = struct_value () [concrete]
+// CHECK:STDOUT:   %type.as.BitAndWith.impl.Op.bound: <bound method> = bound_method %type, %type.as.BitAndWith.impl.Op [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%T) [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core.import_ref.636: %type.as.BitAndWith.impl.Op.type = import_ref Core//prelude/parts/as, loc{{\d+_\d+}}, loaded [concrete = constants.%type.as.BitAndWith.impl.Op]
+// CHECK:STDOUT:   %BitAndWith.impl_witness_table = impl_witness_table (%Core.import_ref.636), @type.as.BitAndWith.impl [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.e25 = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc8_12.1: type = splice_block %.loc8_12.3 [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %E.ref.loc8_10: type = name_ref E, file.%E.decl [concrete = constants.%type]
+// CHECK:STDOUT:       %E.ref.loc8_14: type = name_ref E, file.%E.decl [concrete = constants.%type]
+// CHECK:STDOUT:       %impl.elem0: %.fa7 = impl_witness_access constants.%BitAndWith.impl_witness, element0 [concrete = constants.%type.as.BitAndWith.impl.Op]
+// CHECK:STDOUT:       %bound_method: <bound method> = bound_method %E.ref.loc8_10, %impl.elem0 [concrete = constants.%type.as.BitAndWith.impl.Op.bound]
+// CHECK:STDOUT:       %type.as.BitAndWith.impl.Op.call: init type = call %bound_method(%E.ref.loc8_10, %E.ref.loc8_14) [concrete = constants.%type]
+// CHECK:STDOUT:       %.loc8_12.2: type = value_of_initializer %type.as.BitAndWith.impl.Op.call [concrete = constants.%type]
+// CHECK:STDOUT:       %.loc8_12.3: type = converted %type.as.BitAndWith.impl.Op.call, %.loc8_12.2 [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc8_6.2: %type = bind_symbolic_name T, 0 [symbolic = %T.loc8_6.1 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(%T.loc8_6.2: %type) {
+// CHECK:STDOUT:   %T.loc8_6.1: %type = bind_symbolic_name T, 0 [symbolic = %T.loc8_6.1 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.specific_fn.loc9_3.2: <specific function> = specific_function constants.%F, @F(%T.loc8_6.1) [symbolic = %F.specific_fn.loc9_3.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref: %type = name_ref T, %T.loc8_6.2 [symbolic = %T.loc8_6.1 (constants.%T)]
+// CHECK:STDOUT:     %F.specific_fn.loc9_3.1: <specific function> = specific_function %F.ref, @F(constants.%T) [symbolic = %F.specific_fn.loc9_3.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:     %F.call: init %empty_tuple.type = call %F.specific_fn.loc9_3.1()
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%T) {
+// CHECK:STDOUT:   %T.loc8_6.1 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- compatible_constraints.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %T: %type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %type [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%T) [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %T.patt: %pattern_type = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc9: type = splice_block %E2.ref [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %E2.ref: type = name_ref E2, file.%E2.decl [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc9_6.2: %type = bind_symbolic_name T, 0 [symbolic = %T.loc9_6.1 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(%T.loc9_6.2: %type) {
+// CHECK:STDOUT:   %T.loc9_6.1: %type = bind_symbolic_name T, 0 [symbolic = %T.loc9_6.1 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.specific_fn.loc10_3.2: <specific function> = specific_function constants.%F, @F(%T.loc9_6.1) [symbolic = %F.specific_fn.loc10_3.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref: %type = name_ref T, %T.loc9_6.2 [symbolic = %T.loc9_6.1 (constants.%T)]
+// CHECK:STDOUT:     %F.specific_fn.loc10_3.1: <specific function> = specific_function %F.ref, @F(constants.%T) [symbolic = %F.specific_fn.loc10_3.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:     %F.call: init %empty_tuple.type = call %F.specific_fn.loc10_3.1()
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%T) {
+// CHECK:STDOUT:   %T.loc9_6.1 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 191 - 0
toolchain/check/testdata/named_constraint/empty.carbon

@@ -0,0 +1,191 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/empty.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/empty.carbon
+
+constraint Empty {}
+
+// No constraints, so it matches any type, but you can't do anything with it.
+// Like `type`.
+//
+//@dump-sem-ir-begin
+fn F(T:! Empty) {}
+//@dump-sem-ir-end
+
+interface Z {}
+
+//@dump-sem-ir-begin
+fn G(T:! Z, U:! type, V:! Empty) {
+  F({});
+  F(type);
+  F(T);
+  F(U);
+  F(V);
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- empty.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %T.85e: %type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.e25: type = pattern_type %type [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Z.type: type = facet_type <@Z> [concrete]
+// CHECK:STDOUT:   %T.1d7: %Z.type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.4a0: type = pattern_type %Z.type [concrete]
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U, 1 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %V: %type = bind_symbolic_name V, 2 [symbolic]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %facet_value.6fa: %type = facet_value %empty_struct_type, () [concrete]
+// CHECK:STDOUT:   %F.specific_fn.007: <specific function> = specific_function %F, @F(%facet_value.6fa) [concrete]
+// CHECK:STDOUT:   %facet_value.832: %type = facet_value type, () [concrete]
+// CHECK:STDOUT:   %F.specific_fn.7db: <specific function> = specific_function %F, @F(%facet_value.832) [concrete]
+// CHECK:STDOUT:   %T.binding.as_type: type = symbolic_binding_type T, 0, %T.1d7 [symbolic]
+// CHECK:STDOUT:   %facet_value.f04: %type = facet_value %T.binding.as_type, () [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.59e: <specific function> = specific_function %F, @F(%facet_value.f04) [symbolic]
+// CHECK:STDOUT:   %facet_value.137: %type = facet_value %U, () [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.296: <specific function> = specific_function %F, @F(%facet_value.137) [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.88e: <specific function> = specific_function %F, @F(%V) [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.e25 = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc19: type = splice_block %Empty.ref [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Empty.ref: type = name_ref Empty, file.%Empty.decl [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc19_6.2: %type = bind_symbolic_name T, 0 [symbolic = %T.loc19_6.1 (constants.%T.85e)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.4a0 = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:     %U.patt: %pattern_type.98f = symbolic_binding_pattern U, 1 [concrete]
+// CHECK:STDOUT:     %V.patt: %pattern_type.e25 = symbolic_binding_pattern V, 2 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc25_10: type = splice_block %Z.ref [concrete = constants.%Z.type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Z.ref: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc25_6.2: %Z.type = bind_symbolic_name T, 0 [symbolic = %T.loc25_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %U.loc25_13.2: type = bind_symbolic_name U, 1 [symbolic = %U.loc25_13.1 (constants.%U)]
+// CHECK:STDOUT:     %.loc25_27: type = splice_block %Empty.ref [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Empty.ref: type = name_ref Empty, file.%Empty.decl [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %V.loc25_23.2: %type = bind_symbolic_name V, 2 [symbolic = %V.loc25_23.1 (constants.%V)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F(%T.loc19_6.2: %type) {
+// CHECK:STDOUT:   %T.loc19_6.1: %type = bind_symbolic_name T, 0 [symbolic = %T.loc19_6.1 (constants.%T.85e)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(%T.loc25_6.2: %Z.type, %U.loc25_13.2: type, %V.loc25_23.2: %type) {
+// CHECK:STDOUT:   %T.loc25_6.1: %Z.type = bind_symbolic_name T, 0 [symbolic = %T.loc25_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:   %U.loc25_13.1: type = bind_symbolic_name U, 1 [symbolic = %U.loc25_13.1 (constants.%U)]
+// CHECK:STDOUT:   %V.loc25_23.1: %type = bind_symbolic_name V, 2 [symbolic = %V.loc25_23.1 (constants.%V)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %T.binding.as_type: type = symbolic_binding_type T, 0, %T.loc25_6.1 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:   %facet_value.loc28_6.2: %type = facet_value %T.binding.as_type, () [symbolic = %facet_value.loc28_6.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:   %F.specific_fn.loc28_3.2: <specific function> = specific_function constants.%F, @F(%facet_value.loc28_6.2) [symbolic = %F.specific_fn.loc28_3.2 (constants.%F.specific_fn.59e)]
+// CHECK:STDOUT:   %facet_value.loc29_6.2: %type = facet_value %U.loc25_13.1, () [symbolic = %facet_value.loc29_6.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:   %F.specific_fn.loc29_3.2: <specific function> = specific_function constants.%F, @F(%facet_value.loc29_6.2) [symbolic = %F.specific_fn.loc29_3.2 (constants.%F.specific_fn.296)]
+// CHECK:STDOUT:   %F.specific_fn.loc30_3.2: <specific function> = specific_function constants.%F, @F(%V.loc25_23.1) [symbolic = %F.specific_fn.loc30_3.2 (constants.%F.specific_fn.88e)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     %F.ref.loc26: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %.loc26_6: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:     %facet_value.loc26: %type = facet_value constants.%empty_struct_type, () [concrete = constants.%facet_value.6fa]
+// CHECK:STDOUT:     %.loc26_7: %type = converted %.loc26_6, %facet_value.loc26 [concrete = constants.%facet_value.6fa]
+// CHECK:STDOUT:     %F.specific_fn.loc26: <specific function> = specific_function %F.ref.loc26, @F(constants.%facet_value.6fa) [concrete = constants.%F.specific_fn.007]
+// CHECK:STDOUT:     %F.call.loc26: init %empty_tuple.type = call %F.specific_fn.loc26()
+// CHECK:STDOUT:     %F.ref.loc27: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %facet_value.loc27: %type = facet_value type, () [concrete = constants.%facet_value.832]
+// CHECK:STDOUT:     %.loc27: %type = converted type, %facet_value.loc27 [concrete = constants.%facet_value.832]
+// CHECK:STDOUT:     %F.specific_fn.loc27: <specific function> = specific_function %F.ref.loc27, @F(constants.%facet_value.832) [concrete = constants.%F.specific_fn.7db]
+// CHECK:STDOUT:     %F.call.loc27: init %empty_tuple.type = call %F.specific_fn.loc27()
+// CHECK:STDOUT:     %F.ref.loc28: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref: %Z.type = name_ref T, %T.loc25_6.2 [symbolic = %T.loc25_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %T.as_type: type = facet_access_type %T.ref [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %facet_value.loc28_6.1: %type = facet_value %T.as_type, () [symbolic = %facet_value.loc28_6.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:     %.loc28: %type = converted %T.ref, %facet_value.loc28_6.1 [symbolic = %facet_value.loc28_6.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:     %F.specific_fn.loc28_3.1: <specific function> = specific_function %F.ref.loc28, @F(constants.%facet_value.f04) [symbolic = %F.specific_fn.loc28_3.2 (constants.%F.specific_fn.59e)]
+// CHECK:STDOUT:     %F.call.loc28: init %empty_tuple.type = call %F.specific_fn.loc28_3.1()
+// CHECK:STDOUT:     %F.ref.loc29: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %U.ref: type = name_ref U, %U.loc25_13.2 [symbolic = %U.loc25_13.1 (constants.%U)]
+// CHECK:STDOUT:     %facet_value.loc29_6.1: %type = facet_value %U.ref, () [symbolic = %facet_value.loc29_6.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:     %.loc29: %type = converted %U.ref, %facet_value.loc29_6.1 [symbolic = %facet_value.loc29_6.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:     %F.specific_fn.loc29_3.1: <specific function> = specific_function %F.ref.loc29, @F(constants.%facet_value.137) [symbolic = %F.specific_fn.loc29_3.2 (constants.%F.specific_fn.296)]
+// CHECK:STDOUT:     %F.call.loc29: init %empty_tuple.type = call %F.specific_fn.loc29_3.1()
+// CHECK:STDOUT:     %F.ref.loc30: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %V.ref: %type = name_ref V, %V.loc25_23.2 [symbolic = %V.loc25_23.1 (constants.%V)]
+// CHECK:STDOUT:     %F.specific_fn.loc30_3.1: <specific function> = specific_function %F.ref.loc30, @F(constants.%V) [symbolic = %F.specific_fn.loc30_3.2 (constants.%F.specific_fn.88e)]
+// CHECK:STDOUT:     %F.call.loc30: init %empty_tuple.type = call %F.specific_fn.loc30_3.1()
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.85e) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%T.85e
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%T.1d7, constants.%U, constants.%V) {
+// CHECK:STDOUT:   %T.loc25_6.1 => constants.%T.1d7
+// CHECK:STDOUT:   %U.loc25_13.1 => constants.%U
+// CHECK:STDOUT:   %V.loc25_23.1 => constants.%V
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%facet_value.6fa) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%facet_value.6fa
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%facet_value.832) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%facet_value.832
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%facet_value.f04) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%facet_value.f04
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%facet_value.137) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%facet_value.137
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%V) {
+// CHECK:STDOUT:   %T.loc19_6.1 => constants.%V
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 257 - 0
toolchain/check/testdata/named_constraint/empty_generic.carbon

@@ -0,0 +1,257 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/empty_generic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/empty_generic.carbon
+
+interface Z {}
+
+//@dump-sem-ir-begin
+constraint Empty(T:! type) {}
+
+fn F(U:! type, T:! Empty(U)) {}
+
+fn G(T:! Z, U:! type, V:! Empty(T)) {
+  F(T, {});
+  F(T, type);
+  F(T, T);
+  F(T, U);
+  F(T, V);
+}
+
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- empty_generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Z.type: type = facet_type <@Z> [concrete]
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %T.8b3: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %Empty.type: type = generic_named_constaint_type @Empty [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %empty_struct: %Empty.type = struct_value () [concrete]
+// CHECK:STDOUT:   %U.8b3: type = bind_symbolic_name U, 0 [symbolic]
+// CHECK:STDOUT:   %T.eca: %type = bind_symbolic_name T, 1 [symbolic]
+// CHECK:STDOUT:   %pattern_type.e25: type = pattern_type %type [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %T.1d7: %Z.type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.4a0: type = pattern_type %Z.type [concrete]
+// CHECK:STDOUT:   %U.336: type = bind_symbolic_name U, 1 [symbolic]
+// CHECK:STDOUT:   %T.binding.as_type: type = symbolic_binding_type T, 0, %T.1d7 [symbolic]
+// CHECK:STDOUT:   %V: %type = bind_symbolic_name V, 2 [symbolic]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %facet_value.6fa: %type = facet_value %empty_struct_type, () [concrete]
+// CHECK:STDOUT:   %F.specific_fn.8f2: <specific function> = specific_function %F, @F(%T.binding.as_type, %facet_value.6fa) [symbolic]
+// CHECK:STDOUT:   %facet_value.832: %type = facet_value type, () [concrete]
+// CHECK:STDOUT:   %F.specific_fn.d97: <specific function> = specific_function %F, @F(%T.binding.as_type, %facet_value.832) [symbolic]
+// CHECK:STDOUT:   %facet_value.f04: %type = facet_value %T.binding.as_type, () [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.bd1: <specific function> = specific_function %F, @F(%T.binding.as_type, %facet_value.f04) [symbolic]
+// CHECK:STDOUT:   %facet_value.137: %type = facet_value %U.336, () [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.76a: <specific function> = specific_function %F, @F(%T.binding.as_type, %facet_value.137) [symbolic]
+// CHECK:STDOUT:   %F.specific_fn.685: <specific function> = specific_function %F, @F(%T.binding.as_type, %V) [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %Empty.decl: %Empty.type = constraint_decl @Empty [concrete = constants.%empty_struct] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %T.loc16_18.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc16_18.1 (constants.%T.8b3)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %U.patt: %pattern_type.98f = symbolic_binding_pattern U, 0 [concrete]
+// CHECK:STDOUT:     %T.patt: %pattern_type.e25 = symbolic_binding_pattern T, 1 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %U.loc18_6.2: type = bind_symbolic_name U, 0 [symbolic = %U.loc18_6.1 (constants.%U.8b3)]
+// CHECK:STDOUT:     %.loc18: type = splice_block %type [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Empty.ref: %Empty.type = name_ref Empty, file.%Empty.decl [concrete = constants.%empty_struct]
+// CHECK:STDOUT:       %U.ref: type = name_ref U, %U.loc18_6.2 [symbolic = %U.loc18_6.1 (constants.%U.8b3)]
+// CHECK:STDOUT:       %type: type = facet_type <type> [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc18_16.2: %type = bind_symbolic_name T, 1 [symbolic = %T.loc18_16.1 (constants.%T.eca)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.4a0 = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:     %U.patt: %pattern_type.98f = symbolic_binding_pattern U, 1 [concrete]
+// CHECK:STDOUT:     %V.patt: %pattern_type.e25 = symbolic_binding_pattern V, 2 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc20_10: type = splice_block %Z.ref [concrete = constants.%Z.type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Z.ref: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %T.loc20_6.2: %Z.type = bind_symbolic_name T, 0 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %U.loc20_13.2: type = bind_symbolic_name U, 1 [symbolic = %U.loc20_13.1 (constants.%U.336)]
+// CHECK:STDOUT:     %.loc20_34.1: type = splice_block %type [concrete = constants.%type] {
+// CHECK:STDOUT:       <elided>
+// CHECK:STDOUT:       %Empty.ref: %Empty.type = name_ref Empty, file.%Empty.decl [concrete = constants.%empty_struct]
+// CHECK:STDOUT:       %T.ref.loc20: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:       %T.as_type.loc20: type = facet_access_type %T.ref.loc20 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:       %.loc20_34.2: type = converted %T.ref.loc20, %T.as_type.loc20 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:       %type: type = facet_type <type> [concrete = constants.%type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %V.loc20_23.2: %type = bind_symbolic_name V, 2 [symbolic = %V.loc20_23.1 (constants.%V)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic constraint @Empty(%T.loc16_18.2: type) {
+// CHECK:STDOUT:   %T.loc16_18.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc16_18.1 (constants.%T.8b3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:
+// CHECK:STDOUT:   constraint {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F(%U.loc18_6.2: type, %T.loc18_16.2: %type) {
+// CHECK:STDOUT:   %U.loc18_6.1: type = bind_symbolic_name U, 0 [symbolic = %U.loc18_6.1 (constants.%U.8b3)]
+// CHECK:STDOUT:   %T.loc18_16.1: %type = bind_symbolic_name T, 1 [symbolic = %T.loc18_16.1 (constants.%T.eca)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G(%T.loc20_6.2: %Z.type, %U.loc20_13.2: type, %V.loc20_23.2: %type) {
+// CHECK:STDOUT:   %T.loc20_6.1: %Z.type = bind_symbolic_name T, 0 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:   %U.loc20_13.1: type = bind_symbolic_name U, 1 [symbolic = %U.loc20_13.1 (constants.%U.336)]
+// CHECK:STDOUT:   %T.binding.as_type: type = symbolic_binding_type T, 0, %T.loc20_6.1 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:   %V.loc20_23.1: %type = bind_symbolic_name V, 2 [symbolic = %V.loc20_23.1 (constants.%V)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.specific_fn.loc21_3.2: <specific function> = specific_function constants.%F, @F(%T.binding.as_type, constants.%facet_value.6fa) [symbolic = %F.specific_fn.loc21_3.2 (constants.%F.specific_fn.8f2)]
+// CHECK:STDOUT:   %F.specific_fn.loc22_3.2: <specific function> = specific_function constants.%F, @F(%T.binding.as_type, constants.%facet_value.832) [symbolic = %F.specific_fn.loc22_3.2 (constants.%F.specific_fn.d97)]
+// CHECK:STDOUT:   %facet_value.loc23_9.2: %type = facet_value %T.binding.as_type, () [symbolic = %facet_value.loc23_9.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:   %F.specific_fn.loc23_3.2: <specific function> = specific_function constants.%F, @F(%T.binding.as_type, %facet_value.loc23_9.2) [symbolic = %F.specific_fn.loc23_3.2 (constants.%F.specific_fn.bd1)]
+// CHECK:STDOUT:   %facet_value.loc24_9.2: %type = facet_value %U.loc20_13.1, () [symbolic = %facet_value.loc24_9.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:   %F.specific_fn.loc24_3.2: <specific function> = specific_function constants.%F, @F(%T.binding.as_type, %facet_value.loc24_9.2) [symbolic = %F.specific_fn.loc24_3.2 (constants.%F.specific_fn.76a)]
+// CHECK:STDOUT:   %F.specific_fn.loc25_3.2: <specific function> = specific_function constants.%F, @F(%T.binding.as_type, %V.loc20_23.1) [symbolic = %F.specific_fn.loc25_3.2 (constants.%F.specific_fn.685)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     %F.ref.loc21: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref.loc21: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %.loc21_9: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:     %T.as_type.loc21: type = facet_access_type %T.ref.loc21 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %.loc21_10.1: type = converted %T.ref.loc21, %T.as_type.loc21 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %facet_value.loc21: %type = facet_value constants.%empty_struct_type, () [concrete = constants.%facet_value.6fa]
+// CHECK:STDOUT:     %.loc21_10.2: %type = converted %.loc21_9, %facet_value.loc21 [concrete = constants.%facet_value.6fa]
+// CHECK:STDOUT:     %F.specific_fn.loc21_3.1: <specific function> = specific_function %F.ref.loc21, @F(constants.%T.binding.as_type, constants.%facet_value.6fa) [symbolic = %F.specific_fn.loc21_3.2 (constants.%F.specific_fn.8f2)]
+// CHECK:STDOUT:     %F.call.loc21: init %empty_tuple.type = call %F.specific_fn.loc21_3.1()
+// CHECK:STDOUT:     %F.ref.loc22: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref.loc22: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %T.as_type.loc22: type = facet_access_type %T.ref.loc22 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %.loc22_12.1: type = converted %T.ref.loc22, %T.as_type.loc22 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %facet_value.loc22: %type = facet_value type, () [concrete = constants.%facet_value.832]
+// CHECK:STDOUT:     %.loc22_12.2: %type = converted type, %facet_value.loc22 [concrete = constants.%facet_value.832]
+// CHECK:STDOUT:     %F.specific_fn.loc22_3.1: <specific function> = specific_function %F.ref.loc22, @F(constants.%T.binding.as_type, constants.%facet_value.832) [symbolic = %F.specific_fn.loc22_3.2 (constants.%F.specific_fn.d97)]
+// CHECK:STDOUT:     %F.call.loc22: init %empty_tuple.type = call %F.specific_fn.loc22_3.1()
+// CHECK:STDOUT:     %F.ref.loc23: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref.loc23_5: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %T.ref.loc23_8: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %T.as_type.loc23_9.1: type = facet_access_type %T.ref.loc23_5 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %.loc23_9.1: type = converted %T.ref.loc23_5, %T.as_type.loc23_9.1 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %T.as_type.loc23_9.2: type = facet_access_type %T.ref.loc23_8 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %facet_value.loc23_9.1: %type = facet_value %T.as_type.loc23_9.2, () [symbolic = %facet_value.loc23_9.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:     %.loc23_9.2: %type = converted %T.ref.loc23_8, %facet_value.loc23_9.1 [symbolic = %facet_value.loc23_9.2 (constants.%facet_value.f04)]
+// CHECK:STDOUT:     %F.specific_fn.loc23_3.1: <specific function> = specific_function %F.ref.loc23, @F(constants.%T.binding.as_type, constants.%facet_value.f04) [symbolic = %F.specific_fn.loc23_3.2 (constants.%F.specific_fn.bd1)]
+// CHECK:STDOUT:     %F.call.loc23: init %empty_tuple.type = call %F.specific_fn.loc23_3.1()
+// CHECK:STDOUT:     %F.ref.loc24: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref.loc24: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %U.ref: type = name_ref U, %U.loc20_13.2 [symbolic = %U.loc20_13.1 (constants.%U.336)]
+// CHECK:STDOUT:     %T.as_type.loc24: type = facet_access_type %T.ref.loc24 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %.loc24_9.1: type = converted %T.ref.loc24, %T.as_type.loc24 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %facet_value.loc24_9.1: %type = facet_value %U.ref, () [symbolic = %facet_value.loc24_9.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:     %.loc24_9.2: %type = converted %U.ref, %facet_value.loc24_9.1 [symbolic = %facet_value.loc24_9.2 (constants.%facet_value.137)]
+// CHECK:STDOUT:     %F.specific_fn.loc24_3.1: <specific function> = specific_function %F.ref.loc24, @F(constants.%T.binding.as_type, constants.%facet_value.137) [symbolic = %F.specific_fn.loc24_3.2 (constants.%F.specific_fn.76a)]
+// CHECK:STDOUT:     %F.call.loc24: init %empty_tuple.type = call %F.specific_fn.loc24_3.1()
+// CHECK:STDOUT:     %F.ref.loc25: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:     %T.ref.loc25: %Z.type = name_ref T, %T.loc20_6.2 [symbolic = %T.loc20_6.1 (constants.%T.1d7)]
+// CHECK:STDOUT:     %V.ref: %type = name_ref V, %V.loc20_23.2 [symbolic = %V.loc20_23.1 (constants.%V)]
+// CHECK:STDOUT:     %T.as_type.loc25: type = facet_access_type %T.ref.loc25 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %.loc25: type = converted %T.ref.loc25, %T.as_type.loc25 [symbolic = %T.binding.as_type (constants.%T.binding.as_type)]
+// CHECK:STDOUT:     %F.specific_fn.loc25_3.1: <specific function> = specific_function %F.ref.loc25, @F(constants.%T.binding.as_type, constants.%V) [symbolic = %F.specific_fn.loc25_3.2 (constants.%F.specific_fn.685)]
+// CHECK:STDOUT:     %F.call.loc25: init %empty_tuple.type = call %F.specific_fn.loc25_3.1()
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Empty(constants.%T.8b3) {
+// CHECK:STDOUT:   %T.loc16_18.1 => constants.%T.8b3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Empty(constants.%U.8b3) {
+// CHECK:STDOUT:   %T.loc16_18.1 => constants.%U.8b3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%U.8b3, constants.%T.eca) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%U.8b3
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%T.eca
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Empty(constants.%T.binding.as_type) {
+// CHECK:STDOUT:   %T.loc16_18.1 => constants.%T.binding.as_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G(constants.%T.1d7, constants.%U.336, constants.%V) {
+// CHECK:STDOUT:   %T.loc20_6.1 => constants.%T.1d7
+// CHECK:STDOUT:   %U.loc20_13.1 => constants.%U.336
+// CHECK:STDOUT:   %T.binding.as_type => constants.%T.binding.as_type
+// CHECK:STDOUT:   %V.loc20_23.1 => constants.%V
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.binding.as_type, constants.%facet_value.6fa) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%T.binding.as_type
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%facet_value.6fa
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.binding.as_type, constants.%facet_value.832) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%T.binding.as_type
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%facet_value.832
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.binding.as_type, constants.%facet_value.f04) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%T.binding.as_type
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%facet_value.f04
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.binding.as_type, constants.%facet_value.137) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%T.binding.as_type
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%facet_value.137
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T.binding.as_type, constants.%V) {
+// CHECK:STDOUT:   %U.loc18_6.1 => constants.%T.binding.as_type
+// CHECK:STDOUT:   %T.loc18_16.1 => constants.%V
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 184 - 0
toolchain/check/testdata/named_constraint/generic.carbon

@@ -0,0 +1,184 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/generic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/generic.carbon
+
+// --- generic.carbon
+library "[[@TEST_NAME]]";
+//@include-in-dumps
+
+constraint GenericType(T:! type) {}
+
+interface Z {}
+constraint GenericZ(U:! Z) {}
+
+constraint ForwardDeclaredGeneric(T:! type);
+constraint ForwardDeclaredGeneric(T:! type) {}
+
+// --- fail_mismatched_generic_param_types.carbon
+library "[[@TEST_NAME]]";
+
+constraint ForwardDeclaredGeneric(T:! ());
+// CHECK:STDERR: fail_mismatched_generic_param_types.carbon:[[@LINE+7]]:35: error: type `<pattern for type>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for ()>` [RedeclParamDiffersType]
+// CHECK:STDERR: constraint ForwardDeclaredGeneric(T:! type) {}
+// CHECK:STDERR:                                   ^
+// CHECK:STDERR: fail_mismatched_generic_param_types.carbon:[[@LINE-4]]:35: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
+// CHECK:STDERR: constraint ForwardDeclaredGeneric(T:! ());
+// CHECK:STDERR:                                   ^
+// CHECK:STDERR:
+constraint ForwardDeclaredGeneric(T:! type) {}
+
+// --- fail_mismatched_generic_param_arity.carbon
+library "[[@TEST_NAME]]";
+
+constraint ForwardDeclaredGeneric(T:! type, U:! type);
+// CHECK:STDERR: fail_mismatched_generic_param_arity.carbon:[[@LINE+7]]:1: error: redeclaration differs because of parameter count of 1 [RedeclParamCountDiffers]
+// CHECK:STDERR: constraint ForwardDeclaredGeneric(T:! type) {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_mismatched_generic_param_arity.carbon:[[@LINE-4]]:1: note: previously declared with parameter count of 2 [RedeclParamCountPrevious]
+// CHECK:STDERR: constraint ForwardDeclaredGeneric(T:! type, U:! type);
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+constraint ForwardDeclaredGeneric(T:! type) {}
+
+// --- fail_inside_definition.carbon
+library "[[@TEST_NAME]]";
+
+constraint Generic(T:! type) {}
+// CHECK:STDERR: fail_inside_definition.carbon:[[@LINE+7]]:1: error: redefinition of `constraint Generic` [RedeclRedef]
+// CHECK:STDERR: constraint Generic(T:! type) {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_inside_definition.carbon:[[@LINE-4]]:1: note: previously defined here [RedeclPrevDef]
+// CHECK:STDERR: constraint Generic(T:! type) {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+constraint Generic(T:! type) {}
+
+// CHECK:STDOUT: --- generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %type: type = facet_type <type> [concrete]
+// CHECK:STDOUT:   %.Self: %type = bind_symbolic_name .Self [symbolic_self]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %GenericType.type: type = generic_named_constaint_type @GenericType [concrete]
+// CHECK:STDOUT:   %empty_struct.bd5: %GenericType.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Self.eca39f.1: %type = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %Z.type: type = facet_type <@Z> [concrete]
+// CHECK:STDOUT:   %Self.1d7: %Z.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %U: %Z.type = bind_symbolic_name U, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.4a0: type = pattern_type %Z.type [concrete]
+// CHECK:STDOUT:   %GenericZ.type: type = generic_named_constaint_type @GenericZ [concrete]
+// CHECK:STDOUT:   %empty_struct.05e: %GenericZ.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Self.eca39f.2: %type = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %ForwardDeclaredGeneric.type: type = generic_named_constaint_type @ForwardDeclaredGeneric [concrete]
+// CHECK:STDOUT:   %empty_struct.1a7: %ForwardDeclaredGeneric.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Self.eca39f.3: %type = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .GenericType = %GenericType.decl
+// CHECK:STDOUT:     .Z = %Z.decl
+// CHECK:STDOUT:     .GenericZ = %GenericZ.decl
+// CHECK:STDOUT:     .ForwardDeclaredGeneric = %ForwardDeclaredGeneric.decl.loc9
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %GenericType.decl: %GenericType.type = constraint_decl @GenericType [concrete = constants.%empty_struct.bd5] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.Self: %type = bind_symbolic_name .Self [symbolic_self = constants.%.Self]
+// CHECK:STDOUT:     %T.loc4_24.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_24.1 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Z.decl: type = interface_decl @Z [concrete = constants.%Z.type] {} {}
+// CHECK:STDOUT:   %GenericZ.decl: %GenericZ.type = constraint_decl @GenericZ [concrete = constants.%empty_struct.05e] {
+// CHECK:STDOUT:     %U.patt: %pattern_type.4a0 = symbolic_binding_pattern U, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc7: type = splice_block %Z.ref [concrete = constants.%Z.type] {
+// CHECK:STDOUT:       %.Self: %type = bind_symbolic_name .Self [symbolic_self = constants.%.Self]
+// CHECK:STDOUT:       %Z.ref: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %U.loc7_21.2: %Z.type = bind_symbolic_name U, 0 [symbolic = %U.loc7_21.1 (constants.%U)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ForwardDeclaredGeneric.decl.loc9: %ForwardDeclaredGeneric.type = constraint_decl @ForwardDeclaredGeneric [concrete = constants.%empty_struct.1a7] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.Self.2: %type = bind_symbolic_name .Self [symbolic_self = constants.%.Self]
+// CHECK:STDOUT:     %T.loc9_35.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_35.1 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ForwardDeclaredGeneric.decl.loc10: %ForwardDeclaredGeneric.type = constraint_decl @ForwardDeclaredGeneric [concrete = constants.%empty_struct.1a7] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.Self.1: %type = bind_symbolic_name .Self [symbolic_self = constants.%.Self]
+// CHECK:STDOUT:     %T.loc10: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_35.1 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Z {
+// CHECK:STDOUT:   %Self: %Z.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1d7]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic constraint @GenericType(%T.loc4_24.2: type) {
+// CHECK:STDOUT:   %T.loc4_24.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_24.1 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Self.2: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   constraint {
+// CHECK:STDOUT:     %Self.1: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic constraint @GenericZ(%U.loc7_21.2: %Z.type) {
+// CHECK:STDOUT:   %U.loc7_21.1: %Z.type = bind_symbolic_name U, 0 [symbolic = %U.loc7_21.1 (constants.%U)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Self.2: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   constraint {
+// CHECK:STDOUT:     %Self.1: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic constraint @ForwardDeclaredGeneric(%T.loc9_35.2: type) {
+// CHECK:STDOUT:   %T.loc9_35.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_35.1 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Self.2: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   constraint {
+// CHECK:STDOUT:     %Self.1: %type = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.eca39f.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericType(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_24.1 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericZ(constants.%U) {
+// CHECK:STDOUT:   %U.loc7_21.1 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ForwardDeclaredGeneric(constants.%T) {
+// CHECK:STDOUT:   %T.loc9_35.1 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 74 - 0
toolchain/check/testdata/named_constraint/invalid_members.carbon

@@ -0,0 +1,74 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/named_constraint/invalid_members.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/named_constraint/invalid_members.carbon
+
+// --- todo_fail_invalid_fn.carbon
+library "[[@TEST_NAME]]";
+
+constraint E {
+  // TODO: Any fn in a non-template constraint is an error.
+  fn DeclaredFunction();
+  fn DeclaredMethod[self: Self](b: Self);
+}
+
+constraint F {
+  // TODO: Any fn in a non-template constraint is an error.
+  fn DefinedFunction() {}
+  fn DefinedMethod[self: Self](b: Self) {}
+}
+
+// --- todo_fail_invalid_var.carbon
+library "[[@TEST_NAME]]";
+
+constraint E {
+  // TODO: Any var in a constraint is an error.
+  var c: ();
+}
+
+// --- fail_todo_invalid_var_template.carbon
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_todo_invalid_var_template.carbon:[[@LINE+8]]:1: error: unrecognized declaration introducer [UnrecognizedDecl]
+// CHECK:STDERR: template constraint E {
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_todo_invalid_var_template.carbon:[[@LINE+4]]:1: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+// CHECK:STDERR: template constraint E {
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+template constraint E {
+  // TODO: Any var in a constraint is an error.
+  var c: ();
+}
+
+// --- todo_fail_invalid_let.carbon
+library "[[@TEST_NAME]]";
+
+constraint E {
+  // TODO: Any let in a constraint is an error.
+  var c: ();
+}
+
+// --- fail_todo_invalid_let_template.carbon
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_todo_invalid_let_template.carbon:[[@LINE+8]]:1: error: unrecognized declaration introducer [UnrecognizedDecl]
+// CHECK:STDERR: template constraint E {
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_todo_invalid_let_template.carbon:[[@LINE+4]]:1: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+// CHECK:STDERR: template constraint E {
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+template constraint E {
+  // TODO: Any let in a constraint is an error.
+  var c: ();
+}

+ 17 - 0
toolchain/check/type.cpp

@@ -204,6 +204,14 @@ auto GetGenericInterfaceType(Context& context, SemIR::InterfaceId interface_id,
       context, interface_id, enclosing_specific_id);
 }
 
+auto GetGenericNamedConstraintType(Context& context,
+                                   SemIR::NamedConstraintId named_constraint_id,
+                                   SemIR::SpecificId enclosing_specific_id)
+    -> SemIR::TypeId {
+  return GetCompleteTypeImpl<SemIR::GenericNamedConstraintType>(
+      context, named_constraint_id, enclosing_specific_id);
+}
+
 auto GetInterfaceType(Context& context, SemIR::InterfaceId interface_id,
                       SemIR::SpecificId specific_id) -> SemIR::TypeId {
   return GetTypeImpl<SemIR::FacetType>(
@@ -211,6 +219,15 @@ auto GetInterfaceType(Context& context, SemIR::InterfaceId interface_id,
       FacetTypeFromInterface(context, interface_id, specific_id).facet_type_id);
 }
 
+auto GetNamedConstraintType(Context& context,
+                            SemIR::NamedConstraintId named_constraint_id,
+                            SemIR::SpecificId specific_id) -> SemIR::TypeId {
+  return GetTypeImpl<SemIR::FacetType>(
+      context,
+      FacetTypeFromNamedConstraint(context, named_constraint_id, specific_id)
+          .facet_type_id);
+}
+
 auto GetFacetType(Context& context, const SemIR::FacetTypeInfo& info)
     -> SemIR::TypeId {
   return GetTypeImpl<SemIR::FacetType>(context,

+ 13 - 0
toolchain/check/type.h

@@ -79,10 +79,23 @@ auto GetGenericInterfaceType(Context& context, SemIR::InterfaceId interface_id,
                              SemIR::SpecificId enclosing_specific_id)
     -> SemIR::TypeId;
 
+// Gets a generic named constraint type, which is the type of a name of a
+// generic named constraint, such as the type of `AddWith` given `constraint
+// AddWith(T:! type)`. The returned type will be complete.
+auto GetGenericNamedConstraintType(Context& context,
+                                   SemIR::NamedConstraintId named_constraint_id,
+                                   SemIR::SpecificId enclosing_specific_id)
+    -> SemIR::TypeId;
+
 // Gets the facet type corresponding to a particular interface.
 auto GetInterfaceType(Context& context, SemIR::InterfaceId interface_id,
                       SemIR::SpecificId specific_id) -> SemIR::TypeId;
 
+// Gets the facet type corresponding to a particular named constraint.
+auto GetNamedConstraintType(Context& context,
+                            SemIR::NamedConstraintId named_constraint_id,
+                            SemIR::SpecificId specific_id) -> SemIR::TypeId;
+
 // Gets the facet type for the given `info`.
 auto GetFacetType(Context& context, const SemIR::FacetTypeInfo& info)
     -> SemIR::TypeId;

+ 2 - 1
toolchain/check/type_completion.cpp

@@ -125,7 +125,8 @@ class TypeCompleter {
              SemIR::AssociatedEntityType, SemIR::CppOverloadSetType,
              SemIR::FunctionType, SemIR::FunctionTypeWithSelfType,
              SemIR::GenericClassType, SemIR::GenericInterfaceType,
-             SemIR::InstType, SemIR::UnboundElementType, SemIR::WhereExpr>())
+             SemIR::GenericNamedConstraintType, SemIR::InstType,
+             SemIR::UnboundElementType, SemIR::WhereExpr>())
   auto BuildInfoForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const
       -> SemIR::CompleteTypeInfo {
     // These types have no runtime operations, so we use an empty value

+ 2 - 2
toolchain/lower/file_context.cpp

@@ -915,8 +915,8 @@ template <typename InstT>
            SemIR::AssociatedEntityType, SemIR::CppOverloadSetType,
            SemIR::FacetType, SemIR::FunctionType,
            SemIR::FunctionTypeWithSelfType, SemIR::GenericClassType,
-           SemIR::GenericInterfaceType, SemIR::InstType,
-           SemIR::UnboundElementType, SemIR::WhereExpr>())
+           SemIR::GenericInterfaceType, SemIR::GenericNamedConstraintType,
+           SemIR::InstType, SemIR::UnboundElementType, SemIR::WhereExpr>())
 static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
     -> llvm::Type* {
   // Return an empty struct as a placeholder.

+ 2 - 0
toolchain/parse/node_ids.h

@@ -162,6 +162,8 @@ using AnyFunctionDeclId = NodeIdOneOf<FunctionDeclId, FunctionDefinitionStartId,
 using AnyImplDeclId = NodeIdOneOf<ImplDeclId, ImplDefinitionStartId>;
 using AnyInterfaceDeclId =
     NodeIdOneOf<InterfaceDeclId, InterfaceDefinitionStartId>;
+using AnyNamedConstraintDeclId =
+    NodeIdOneOf<NamedConstraintDeclId, NamedConstraintDefinitionStartId>;
 using AnyNamespaceId =
     NodeIdOneOf<NamespaceId, ImportDeclId, LibraryDeclId, PackageDeclId>;
 using AnyPackagingDeclId =

+ 27 - 10
toolchain/parse/testdata/generics/named_constraint/basic.carbon

@@ -8,24 +8,41 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/named_constraint/basic.carbon
 
-constraint Foo {
-  fn Baz();
+// --- empty.carbon
+
+constraint Empty {}
+
+// --- with_require.carbon
+
+constraint WithRequire {
+  // TODO: Add a require statement.
 }
 
+// --- forward_decl.carbon
+
 constraint ForwardDeclared;
 
-// CHECK:STDOUT: - filename: basic.carbon
+// CHECK:STDOUT: - filename: empty.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'NamedConstraintIntroducer', text: 'constraint'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Empty'},
+// CHECK:STDOUT:       {kind: 'NamedConstraintDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'NamedConstraintDefinition', text: '}', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: with_require.carbon
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:         {kind: 'NamedConstraintIntroducer', text: 'constraint'},
-// CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'WithRequire'},
 // CHECK:STDOUT:       {kind: 'NamedConstraintDefinitionStart', text: '{', subtree_size: 3},
-// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'Baz'},
-// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
-// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
-// CHECK:STDOUT:       {kind: 'FunctionDecl', text: ';', subtree_size: 5},
-// CHECK:STDOUT:     {kind: 'NamedConstraintDefinition', text: '}', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'NamedConstraintDefinition', text: '}', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: forward_decl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'NamedConstraintIntroducer', text: 'constraint'},
 // CHECK:STDOUT:       {kind: 'IdentifierNameNotBeforeParams', text: 'ForwardDeclared'},
 // CHECK:STDOUT:     {kind: 'NamedConstraintDecl', text: ';', subtree_size: 3},

+ 30 - 5
toolchain/parse/testdata/generics/named_constraint/defined_method.carbon → toolchain/parse/testdata/generics/named_constraint/invalid_method.carbon

@@ -4,22 +4,47 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/generics/named_constraint/defined_method.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/generics/named_constraint/invalid_method.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/named_constraint/defined_method.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/named_constraint/invalid_method.carbon
+
+constraint E {
+  // This is an error in the check phase.
+  fn DeclaredMethod[self: Self](b: Self) -> Self;
+}
 
 constraint Foo {
-  fn Add[self: Self](b: Self) -> Self {}
+  // This is an error in the check phase.
+  fn DefinedMethod[self: Self](b: Self) -> Self {}
 }
 
-// CHECK:STDOUT: - filename: defined_method.carbon
+// CHECK:STDOUT: - filename: invalid_method.carbon
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:         {kind: 'NamedConstraintIntroducer', text: 'constraint'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'E'},
+// CHECK:STDOUT:       {kind: 'NamedConstraintDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'DeclaredMethod'},
+// CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:             {kind: 'SelfValueName', text: 'self'},
+// CHECK:STDOUT:             {kind: 'SelfTypeNameExpr', text: 'Self'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'b'},
+// CHECK:STDOUT:             {kind: 'SelfTypeNameExpr', text: 'Self'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'SelfTypeNameExpr', text: 'Self'},
+// CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDecl', text: ';', subtree_size: 15},
+// CHECK:STDOUT:     {kind: 'NamedConstraintDefinition', text: '}', subtree_size: 19},
+// CHECK:STDOUT:         {kind: 'NamedConstraintIntroducer', text: 'constraint'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
 // CHECK:STDOUT:       {kind: 'NamedConstraintDefinitionStart', text: '{', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:           {kind: 'IdentifierNameBeforeParams', text: 'Add'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameBeforeParams', text: 'DefinedMethod'},
 // CHECK:STDOUT:             {kind: 'ImplicitParamListStart', text: '['},
 // CHECK:STDOUT:               {kind: 'SelfValueName', text: 'self'},
 // CHECK:STDOUT:               {kind: 'SelfTypeNameExpr', text: 'Self'},

+ 37 - 0
toolchain/parse/testdata/generics/named_constraint/template_constraint.carbon

@@ -0,0 +1,37 @@
+// 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/parse/testdata/generics/named_constraint/template_constraint.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/named_constraint/template_constraint.carbon
+
+// --- fail_todo_template_constraint.carbon
+
+// CHECK:STDERR: fail_todo_template_constraint.carbon:[[@LINE+4]]:1: error: unrecognized declaration introducer [UnrecognizedDecl]
+// CHECK:STDERR: template constraint Foo {
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+template constraint Foo {
+  // TODO: Add a require statement.
+  fn DeclaredMethod();
+  fn DefinedMethod() {}
+}
+
+// CHECK:STDERR: fail_todo_template_constraint.carbon:[[@LINE+4]]:1: error: unrecognized declaration introducer [UnrecognizedDecl]
+// CHECK:STDERR: template constraint ForwardDeclared;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+template constraint ForwardDeclared;
+
+// CHECK:STDOUT: - filename: fail_todo_template_constraint.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'InvalidParseStart', text: 'template', has_error: yes},
+// CHECK:STDOUT:     {kind: 'InvalidParseSubtree', text: '}', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'InvalidParseStart', text: 'template', has_error: yes},
+// CHECK:STDOUT:     {kind: 'InvalidParseSubtree', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -102,6 +102,7 @@ cc_library(
         "interface.h",
         "name.h",
         "name_scope.h",
+        "named_constraint.h",
         "pattern.h",
         "struct_type_field.h",
         "type.h",

+ 2 - 0
toolchain/sem_ir/expr_info.cpp

@@ -126,6 +126,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case FunctionTypeWithSelfType::Kind:
       case GenericClassType::Kind:
       case GenericInterfaceType::Kind:
+      case GenericNamedConstraintType::Kind:
       case LookupImplWitness::Kind:
       case ImplWitness::Kind:
       case ImplWitnessAccess::Kind:
@@ -141,6 +142,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case IntValue::Kind:
       case InterfaceDecl::Kind:
       case MaybeUnformedType::Kind:
+      case NamedConstraintDecl::Kind:
       case NamespaceType::Kind:
       case PartialType::Kind:
       case PatternType::Kind:

+ 10 - 0
toolchain/sem_ir/file.h

@@ -35,6 +35,7 @@
 #include "toolchain/sem_ir/interface.h"
 #include "toolchain/sem_ir/name.h"
 #include "toolchain/sem_ir/name_scope.h"
+#include "toolchain/sem_ir/named_constraint.h"
 #include "toolchain/sem_ir/singleton_insts.h"
 #include "toolchain/sem_ir/specific_interface.h"
 #include "toolchain/sem_ir/struct_type_field.h"
@@ -171,6 +172,12 @@ class File : public Printable<File> {
   auto classes() const -> const ClassStore& { return classes_; }
   auto interfaces() -> InterfaceStore& { return interfaces_; }
   auto interfaces() const -> const InterfaceStore& { return interfaces_; }
+  auto named_constraints() -> NamedConstraintStore& {
+    return named_constraints_;
+  }
+  auto named_constraints() const -> const NamedConstraintStore& {
+    return named_constraints_;
+  }
   auto associated_constants() -> AssociatedConstantStore& {
     return associated_constants_;
   }
@@ -321,6 +328,9 @@ class File : public Printable<File> {
   // Storage for interfaces.
   InterfaceStore interfaces_;
 
+  // Storage for named constraints.
+  NamedConstraintStore named_constraints_;
+
   // Storage for associated constants.
   AssociatedConstantStore associated_constants_;
 

+ 47 - 0
toolchain/sem_ir/formatter.cpp

@@ -80,6 +80,11 @@ auto Formatter::Format() -> void {
     FormatInterface(id, interface);
   }
 
+  for (const auto& [id, constraint] :
+       sem_ir_->named_constraints().enumerate()) {
+    FormatNamedConstraint(id, constraint);
+  }
+
   for (const auto& [id, assoc_const] :
        sem_ir_->associated_constants().enumerate()) {
     FormatAssociatedConstant(id, assoc_const);
@@ -417,6 +422,39 @@ auto Formatter::FormatInterface(InterfaceId id, const Interface& interface_info)
   FormatEntityEnd(interface_info.generic_id);
 }
 
+auto Formatter::FormatNamedConstraint(NamedConstraintId id,
+                                      const NamedConstraint& constraint_info)
+    -> void {
+  if (!ShouldFormatEntity(constraint_info)) {
+    return;
+  }
+
+  FormatEntityStart("constraint", constraint_info, id);
+
+  llvm::SaveAndRestore constraint_scope(scope_, inst_namer_.GetScopeFor(id));
+
+  if (constraint_info.scope_id.has_value()) {
+    out_ << ' ';
+    OpenBrace();
+    FormatCodeBlock(constraint_info.body_block_id);
+
+    // Always include the !members label because we always list the witness in
+    // this section.
+    IndentLabel();
+    out_ << "!members:\n";
+    FormatNameScope(constraint_info.scope_id);
+
+    // TODO: Print `require` statements.
+
+    CloseBrace();
+  } else {
+    Semicolon();
+  }
+  out_ << '\n';
+
+  FormatEntityEnd(constraint_info.generic_id);
+}
+
 auto Formatter::FormatAssociatedConstant(AssociatedConstantId id,
                                          const AssociatedConstant& assoc_const)
     -> void {
@@ -1084,6 +1122,15 @@ auto Formatter::FormatInstRhs(Inst inst) -> void {
       return;
     }
 
+    case CARBON_KIND(NamedConstraintDecl decl): {
+      FormatDeclRhs(decl.named_constraint_id,
+                    sem_ir_->named_constraints()
+                        .Get(decl.named_constraint_id)
+                        .pattern_block_id,
+                    decl.decl_block_id);
+      return;
+    }
+
     case CARBON_KIND(Namespace ns): {
       if (ns.import_id.has_value()) {
         FormatArgs(ns.import_id, ns.name_scope_id);

+ 4 - 0
toolchain/sem_ir/formatter.h

@@ -158,6 +158,10 @@ class Formatter {
   // Formats a full interface.
   auto FormatInterface(InterfaceId id, const Interface& interface_info) -> void;
 
+  // Formats a full named constraint.
+  auto FormatNamedConstraint(NamedConstraintId id,
+                             const NamedConstraint& constraint_info) -> void;
+
   // Formats an associated constant entity.
   auto FormatAssociatedConstant(AssociatedConstantId id,
                                 const AssociatedConstant& assoc_const) -> void;

+ 1 - 0
toolchain/sem_ir/id_kind.h

@@ -60,6 +60,7 @@ using IdKind = TypeEnum<
     MetaInstId,
     NameId,
     NameScopeId,
+    NamedConstraintId,
     SpecificId,
     SpecificInterfaceId,
     StructTypeFieldsId,

+ 7 - 0
toolchain/sem_ir/ids.h

@@ -327,6 +327,13 @@ struct InterfaceId : public IdBase<InterfaceId> {
   using IdBase::IdBase;
 };
 
+// The ID of a `NamedConstraint`.
+struct NamedConstraintId : public IdBase<NamedConstraintId> {
+  static constexpr llvm::StringLiteral Label = "constraint";
+
+  using IdBase::IdBase;
+};
+
 // The ID of an `AssociatedConstant`.
 struct AssociatedConstantId : public IdBase<AssociatedConstantId> {
   static constexpr llvm::StringLiteral Label = "assoc_const";

+ 4 - 0
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -238,6 +238,10 @@ struct Worklist {
     AddEntity(sem_ir->interfaces().Get(interface_id));
   }
 
+  auto Add(NamedConstraintId named_constraint_id) -> void {
+    AddEntity(sem_ir->named_constraints().Get(named_constraint_id));
+  }
+
   auto Add(AssociatedConstantId assoc_const_id) -> void {
     AddEntity<AssociatedConstant>(
         sem_ir->associated_constants().Get(assoc_const_id));

+ 2 - 0
toolchain/sem_ir/inst_kind.def

@@ -76,6 +76,7 @@ CARBON_SEM_IR_INST_KIND(FunctionType)
 CARBON_SEM_IR_INST_KIND(FunctionTypeWithSelfType)
 CARBON_SEM_IR_INST_KIND(GenericClassType)
 CARBON_SEM_IR_INST_KIND(GenericInterfaceType)
+CARBON_SEM_IR_INST_KIND(GenericNamedConstraintType)
 CARBON_SEM_IR_INST_KIND(ImplDecl)
 CARBON_SEM_IR_INST_KIND(ImplWitness)
 CARBON_SEM_IR_INST_KIND(ImplWitnessAccess)
@@ -98,6 +99,7 @@ CARBON_SEM_IR_INST_KIND(InterfaceDecl)
 CARBON_SEM_IR_INST_KIND(LookupImplWitness)
 CARBON_SEM_IR_INST_KIND(MaybeUnformedType)
 CARBON_SEM_IR_INST_KIND(NameBindingDecl)
+CARBON_SEM_IR_INST_KIND(NamedConstraintDecl)
 CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)
 CARBON_SEM_IR_INST_KIND(NamespaceType)

+ 35 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -155,6 +155,10 @@ auto InstNamer::GetScopeIdOffset(ScopeIdTypeEnum id_enum) const -> int {
       [[fallthrough]];
     case ScopeIdTypeEnum::For<InterfaceId>:
 
+      offset += sem_ir_->named_constraints().size();
+      [[fallthrough]];
+    case ScopeIdTypeEnum::For<NamedConstraintId>:
+
       offset += sem_ir_->specific_interfaces().size();
       [[fallthrough]];
     case ScopeIdTypeEnum::For<SpecificInterfaceId>:
@@ -546,6 +550,9 @@ auto InstNamer::GetNameForParentNameScope(NameScopeId name_scope_id)
     case CARBON_KIND(InterfaceDecl interface): {
       return MaybePushEntity(interface.interface_id);
     }
+    case CARBON_KIND(NamedConstraintDecl named_constraint): {
+      return MaybePushEntity(named_constraint.named_constraint_id);
+    }
     case SemIR::Namespace::Kind: {
       // Only prefix type scopes.
       return "";
@@ -645,6 +652,23 @@ auto InstNamer::PushEntity(InterfaceId interface_id, ScopeId scope_id,
   PushBlockId(scope_id, interface.pattern_block_id);
 }
 
+auto InstNamer::PushEntity(NamedConstraintId named_constraint_id,
+                           ScopeId scope_id, Scope& scope) -> void {
+  const auto& constraint =
+      sem_ir_->named_constraints().Get(named_constraint_id);
+  LocId constraint_loc(constraint.latest_decl_id());
+  scope.name = globals_.AllocateName(
+      *this, constraint_loc,
+      sem_ir_->names().GetIRBaseName(constraint.name_id).str());
+  AddBlockLabel(scope_id, constraint.body_block_id, "constraint",
+                constraint_loc);
+
+  // Push blocks in reverse order.
+  PushGeneric(scope_id, constraint.generic_id);
+  PushBlockId(scope_id, constraint.body_block_id);
+  PushBlockId(scope_id, constraint.pattern_block_id);
+}
+
 auto InstNamer::PushEntity(VtableId vtable_id, ScopeId /*scope_id*/,
                            Scope& scope) -> void {
   const auto& vtable = sem_ir_->vtables().Get(vtable_id);
@@ -924,6 +948,10 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddEntityNameAndMaybePush(inst.interface_id, ".type");
       return;
     }
+    case CARBON_KIND(GenericNamedConstraintType inst): {
+      AddEntityNameAndMaybePush(inst.named_constraint_id, ".type");
+      return;
+    }
     case CARBON_KIND(ImplDecl inst): {
       // `impl` declarations aren't named because they aren't added to any
       // namespace, and so aren't referenced directly.
@@ -1046,6 +1074,13 @@ auto InstNamer::NamingContext::NameInst() -> void {
       PushBlockId(scope_id_, inst.pattern_block_id);
       return;
     }
+    case CARBON_KIND(NamedConstraintDecl inst): {
+      AddEntityNameAndMaybePush(inst.named_constraint_id, ".decl");
+      auto interface_scope_id =
+          inst_namer_->GetScopeFor(inst.named_constraint_id);
+      PushBlockId(interface_scope_id, inst.decl_block_id);
+      return;
+    }
     case CARBON_KIND(NameRef inst): {
       AddInstNameId(inst.name_id, ".ref");
       return;

+ 4 - 1
toolchain/sem_ir/inst_namer.h

@@ -39,7 +39,8 @@ class InstNamer {
   // Entities whose scopes get entries from `ScopeId`.
   using ScopeIdTypeEnum =
       TypeEnum<AssociatedConstantId, ClassId, CppOverloadSetId, FunctionId,
-               ImplId, InterfaceId, SpecificInterfaceId, VtableId>;
+               ImplId, InterfaceId, NamedConstraintId, SpecificInterfaceId,
+               VtableId>;
 
   // Construct the instruction namer, and assign names to all instructions in
   // the provided file.
@@ -218,6 +219,8 @@ class InstNamer {
   auto PushEntity(ImplId impl_id, ScopeId scope_id, Scope& scope) -> void;
   auto PushEntity(InterfaceId interface_id, ScopeId scope_id, Scope& scope)
       -> void;
+  auto PushEntity(NamedConstraintId named_constraint_id, ScopeId scope_id,
+                  Scope& scope) -> void;
   auto PushEntity(VtableId vtable_id, ScopeId scope_id, Scope& scope) -> void;
 
   // Always returns the name of the entity. May push it if it has not yet been

+ 54 - 0
toolchain/sem_ir/named_constraint.h

@@ -0,0 +1,54 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_NAMED_CONSTRAINT_H_
+#define CARBON_TOOLCHAIN_SEM_IR_NAMED_CONSTRAINT_H_
+
+#include "toolchain/base/value_store.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// Named constraint-specific fields.
+struct NamedConstraintFields {
+  // The following members are set at the `{` of the constraint definition.
+
+  // The constraint scope.
+  NameScopeId scope_id = NameScopeId::None;
+  // The first block of the constraint body.
+  InstBlockId body_block_id = InstBlockId::None;
+  // The implicit `Self` parameter. This is a BindSymbolicName instruction.
+  InstId self_param_id = InstId::None;
+
+  // The following members are set at the `}` of the constraint definition.
+
+  bool complete = false;
+};
+
+// A named constraint. See EntityWithParamsBase regarding the inheritance here.
+struct NamedConstraint : public EntityWithParamsBase,
+                         public NamedConstraintFields,
+                         public Printable<NamedConstraint> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{";
+    PrintBaseFields(out);
+    out << "}";
+  }
+
+  // This is false until we reach the `}` of the constraint definition.
+  auto is_complete() const -> bool { return complete; }
+
+  // Determines whether we're currently defining the constraint. This is true
+  // between the braces of the constraint.
+  auto is_being_defined() const -> bool {
+    return has_definition_started() && !is_complete();
+  }
+};
+
+using NamedConstraintStore = ValueStore<NamedConstraintId, NamedConstraint>;
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_NAMED_CONSTRAINT_H_

+ 10 - 0
toolchain/sem_ir/stringify.cpp

@@ -437,6 +437,16 @@ class Stringifier {
                       ">");
   }
 
+  auto StringifyInst(InstId /*inst_id*/, GenericNamedConstraintType inst)
+      -> void {
+    const auto& constraint =
+        sem_ir_->named_constraints().Get(inst.named_constraint_id);
+    *out_ << "<type of ";
+    step_stack_->Push(StepStack::QualifiedNameItem{constraint.parent_scope_id,
+                                                   constraint.name_id},
+                      ">");
+  }
+
   // Determine the specific interface that an impl witness instruction provides
   // an implementation of.
   // TODO: Should we track this in the type?

+ 30 - 0
toolchain/sem_ir/typed_insts.h

@@ -856,6 +856,21 @@ struct GenericInterfaceType {
   SpecificId enclosing_specific_id;
 };
 
+// The type of the name of a generic named constraint. The corresponding value
+// is an empty `StructValue`.
+struct GenericNamedConstraintType {
+  // This is only ever created as a constant, so doesn't have a location.
+  static constexpr auto Kind =
+      InstKind::GenericNamedConstraintType.Define<Parse::NoneNodeId>(
+          {.ir_name = "generic_named_constaint_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::WheneverPossible});
+
+  TypeId type_id;
+  NamedConstraintId named_constraint_id;
+  SpecificId enclosing_specific_id;
+};
+
 // An `impl` declaration.
 struct ImplDecl {
   static constexpr auto Kind = InstKind::ImplDecl.Define<Parse::AnyImplDeclId>(
@@ -1115,6 +1130,7 @@ struct InterfaceDecl {
       InstKind::InterfaceDecl.Define<Parse::AnyInterfaceDeclId>(
           {.ir_name = "interface_decl", .is_lowered = false});
 
+  // Always `type`.
   TypeId type_id;
   InterfaceId interface_id;
   // The declaration block, containing the interface name's qualifiers and the
@@ -1220,6 +1236,20 @@ struct NameBindingDecl {
   InstBlockId pattern_block_id;
 };
 
+// A named constraint declaration.
+struct NamedConstraintDecl {
+  static constexpr auto Kind =
+      InstKind::NamedConstraintDecl.Define<Parse::AnyNamedConstraintDeclId>(
+          {.ir_name = "constraint_decl", .is_lowered = false});
+
+  // Always `type`.
+  TypeId type_id;
+  NamedConstraintId named_constraint_id;
+  // The declaration block, containing the constraint name's qualifiers and the
+  // constraint's generic parameters.
+  DeclInstBlockId decl_block_id;
+};
+
 // A name reference, with the value of the name. This only handles name
 // resolution; the value may be used for reading or writing.
 struct NameRef {