Procházet zdrojové kódy

Introduce TypeInstId (#5288)

TypeInstId is an InstId whose constant value has a type of TypeType.
This includes:
- Type value instructions, the `ClassType` or `IntLiteralType`
instructions.
- Constraint value instructions, which are the `FacetType` and
`TypeType` instructions, each of which also have type TypeType.

TypeInstId encodes in the type system that it is safe to convert the
instruction's value to a TypeId, and CHECKs at construction that this
invariant is maintained.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Dana Jansens před 1 rokem
rodič
revize
cf57c85545

+ 7 - 7
toolchain/check/convert.cpp

@@ -1076,16 +1076,16 @@ static auto PerformBuiltinConversion(
         // `FacetValue`, which requires an instruction of type `TypeType`. So if
         // we are converting from a facet value, we get its `type` via an extra
         // `FacetAccessType` instruction.
-        auto type_inst_id = SemIR::InstId::None;
+        auto type_inst_id = SemIR::TypeInstId::None;
         if (sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
-          type_inst_id =
+          type_inst_id = context.types().GetAsTypeInstId(
               AddInst(context, loc_id,
                       SemIR::FacetAccessType{
                           .type_id = SemIR::TypeType::SingletonTypeId,
                           .facet_value_inst_id = const_value_id,
-                      });
+                      }));
         } else {
-          type_inst_id = const_value_id;
+          type_inst_id = context.types().GetAsTypeInstId(const_value_id);
         }
         return AddInst<SemIR::FacetValue>(
             context, loc_id,
@@ -1470,7 +1470,7 @@ auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
   auto type_inst_id = ConvertToValueOfType(context, loc_id, value_id,
                                            SemIR::TypeType::SingletonTypeId);
   if (type_inst_id == SemIR::ErrorInst::SingletonInstId) {
-    return {.inst_id = type_inst_id,
+    return {.inst_id = SemIR::ErrorInst::SingletonTypeInstId,
             .type_id = SemIR::ErrorInst::SingletonTypeId};
   }
 
@@ -1481,11 +1481,11 @@ auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
                         "cannot evaluate type expression");
       context.emitter().Emit(loc_id, TypeExprEvaluationFailure);
     }
-    return {.inst_id = SemIR::ErrorInst::SingletonInstId,
+    return {.inst_id = SemIR::ErrorInst::SingletonTypeInstId,
             .type_id = SemIR::ErrorInst::SingletonTypeId};
   }
 
-  return {.inst_id = type_inst_id,
+  return {.inst_id = context.types().GetAsTypeInstId(type_inst_id),
           .type_id = context.types().GetTypeIdForTypeConstantId(type_const_id)};
 }
 

+ 1 - 1
toolchain/check/convert.h

@@ -120,7 +120,7 @@ auto ConvertCallArgs(Context& context, SemIR::LocId call_loc_id,
 // A type that has been converted for use as a type expression.
 struct TypeExpr {
   // The converted expression of type `type`, or `ErrorInst::SingletonInstId`.
-  SemIR::InstId inst_id;
+  SemIR::TypeInstId inst_id;
   // The corresponding type, or `ErrorInst::SingletonTypeId`.
   SemIR::TypeId type_id;
 };

+ 4 - 0
toolchain/check/deduce.cpp

@@ -130,6 +130,10 @@ class DeductionWorklist {
         Add(inst_id, SemIR::InstId(arg), needs_substitution);
         break;
       }
+      case CARBON_KIND(SemIR::TypeInstId inst_id): {
+        Add(inst_id, SemIR::InstId(arg), needs_substitution);
+        break;
+      }
       case CARBON_KIND(SemIR::StructTypeFieldsId fields_id): {
         AddAll(fields_id, SemIR::StructTypeFieldsId(arg), needs_substitution);
         break;

+ 10 - 0
toolchain/check/eval.cpp

@@ -421,6 +421,16 @@ static auto GetConstantValue(EvalContext& eval_context,
   return inst_id;
 }
 
+static auto GetConstantValue(EvalContext& eval_context,
+                             SemIR::TypeInstId inst_id, Phase* phase)
+    -> SemIR::TypeInstId {
+  // The input instruction is a TypeInstId, and eval does not change concrete
+  // types (like TypeType which TypeInstId implies), so the result is also a
+  // valid TypeInstId.
+  return SemIR::TypeInstId::UnsafeMake(GetConstantValue(
+      eval_context, static_cast<SemIR::InstId>(inst_id), phase));
+}
+
 // Explicitly discard a `DestInstId`, because we should not be using the
 // destination as part of evaluation.
 static auto GetConstantValue(EvalContext& /*eval_context*/,

+ 4 - 4
toolchain/check/facet_type.cpp

@@ -39,7 +39,7 @@ static auto WitnessAccessMatchesInterface(
 }
 
 static auto IncompleteFacetTypeDiagnosticBuilder(
-    Context& context, SemIRLoc loc, SemIR::InstId facet_type_inst_id,
+    Context& context, SemIRLoc loc, SemIR::TypeInstId facet_type_inst_id,
     bool is_definition) -> DiagnosticBuilder {
   if (is_definition) {
     CARBON_DIAGNOSTIC(ImplAsIncompleteFacetTypeDefinition, Error,
@@ -59,7 +59,7 @@ static auto IncompleteFacetTypeDiagnosticBuilder(
 
 auto InitialFacetTypeImplWitness(
     Context& context, SemIR::LocId witness_loc_id,
-    SemIR::InstId facet_type_inst_id, SemIR::InstId self_type_inst_id,
+    SemIR::TypeInstId facet_type_inst_id, SemIR::TypeInstId self_type_inst_id,
     const SemIR::SpecificInterface& interface_to_witness,
     SemIR::SpecificId self_specific_id, bool is_definition) -> SemIR::InstId {
   // TODO: Finish facet type resolution. This code currently only handles
@@ -233,8 +233,8 @@ auto InitialFacetTypeImplWitness(
   return witness_inst_id;
 }
 
-auto RequireCompleteFacetTypeForImplDefinition(Context& context, SemIRLoc loc,
-                                               SemIR::InstId facet_type_inst_id)
+auto RequireCompleteFacetTypeForImplDefinition(
+    Context& context, SemIRLoc loc, SemIR::TypeInstId facet_type_inst_id)
     -> bool {
   auto facet_type_id =
       context.types().GetTypeIdForTypeInstId(facet_type_inst_id);

+ 3 - 3
toolchain/check/facet_type.h

@@ -41,14 +41,14 @@ auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
 // `Self` type.
 auto InitialFacetTypeImplWitness(
     Context& context, SemIR::LocId witness_loc_id,
-    SemIR::InstId facet_type_inst_id, SemIR::InstId self_type_inst_id,
+    SemIR::TypeInstId facet_type_inst_id, SemIR::TypeInstId self_type_inst_id,
     const SemIR::SpecificInterface& interface_to_witness,
     SemIR::SpecificId self_specific_id, bool is_definition) -> SemIR::InstId;
 
 // Returns `true` if the facet type is complete. Otherwise issues a diagnostic
 // and returns `false`.
-auto RequireCompleteFacetTypeForImplDefinition(Context& context, SemIRLoc loc,
-                                               SemIR::InstId facet_type_inst_id)
+auto RequireCompleteFacetTypeForImplDefinition(
+    Context& context, SemIRLoc loc, SemIR::TypeInstId facet_type_inst_id)
     -> bool;
 
 // Replaces the placeholder created by `InitialFacetTypeImplWitness` with an

+ 1 - 1
toolchain/check/handle_binding_pattern.cpp

@@ -143,7 +143,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
                                          cast_type_id);
         });
     if (cast_type_id == SemIR::ErrorInst::SingletonTypeId) {
-      cast_type_inst_id = SemIR::ErrorInst::SingletonInstId;
+      cast_type_inst_id = SemIR::ErrorInst::SingletonTypeInstId;
     }
     auto binding_id =
         context.parse_tree().As<Parse::VarBindingPatternId>(node_id);

+ 9 - 9
toolchain/check/handle_class.cpp

@@ -370,7 +370,7 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
     return true;
   }
 
-  auto [adapted_inst_id, adapted_type_id] =
+  auto [adapted_type_inst_id, adapted_type_id] =
       ExprAsType(context, node_id, adapted_type_expr_id);
   adapted_type_id = AsConcreteType(
       context, adapted_type_id, node_id,
@@ -379,26 +379,26 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
                           "adapted type {0} is an incomplete type",
                           InstIdAsType);
         return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
-                                       adapted_inst_id);
+                                       adapted_type_inst_id);
       },
       [&] {
         CARBON_DIAGNOSTIC(AbstractTypeInAdaptDecl, Error,
                           "adapted type {0} is an abstract type", InstIdAsType);
         return context.emitter().Build(node_id, AbstractTypeInAdaptDecl,
-                                       adapted_inst_id);
+                                       adapted_type_inst_id);
       });
   if (adapted_type_id == SemIR::ErrorInst::SingletonTypeId) {
-    adapted_inst_id = SemIR::ErrorInst::SingletonInstId;
+    adapted_type_inst_id = SemIR::ErrorInst::SingletonTypeInstId;
   }
 
   // Build a SemIR representation for the declaration.
   class_info.adapt_id = AddInst<SemIR::AdaptDecl>(
-      context, node_id, {.adapted_type_inst_id = adapted_inst_id});
+      context, node_id, {.adapted_type_inst_id = adapted_type_inst_id});
 
   // Extend the class scope with the adapted type's scope if requested.
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
     auto& class_scope = context.name_scopes().Get(class_info.scope_id);
-    class_scope.AddExtendedScope(adapted_inst_id);
+    class_scope.AddExtendedScope(adapted_type_inst_id);
   }
   return true;
 }
@@ -422,17 +422,17 @@ struct BaseInfo {
 
   SemIR::TypeId type_id;
   SemIR::NameScopeId scope_id;
-  SemIR::InstId inst_id;
+  SemIR::TypeInstId inst_id;
 };
 constexpr BaseInfo BaseInfo::Error = {
     .type_id = SemIR::ErrorInst::SingletonTypeId,
     .scope_id = SemIR::NameScopeId::None,
-    .inst_id = SemIR::ErrorInst::SingletonInstId};
+    .inst_id = SemIR::ErrorInst::SingletonTypeInstId};
 }  // namespace
 
 // Diagnoses an attempt to derive from a final type.
 static auto DiagnoseBaseIsFinal(Context& context, Parse::NodeId node_id,
-                                SemIR::InstId base_type_inst_id) -> void {
+                                SemIR::TypeInstId base_type_inst_id) -> void {
   CARBON_DIAGNOSTIC(BaseIsFinal, Error,
                     "deriving from final type {0}; base type must be an "
                     "`abstract` or `base` class",

+ 4 - 3
toolchain/check/handle_function.cpp

@@ -53,7 +53,7 @@ auto HandleParseNode(Context& context, Parse::FunctionIntroducerId node_id)
 auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
   // Propagate the type expression.
   auto [type_node_id, type_inst_id] = context.node_stack().PopExprWithNodeId();
-  auto type_id = ExprAsType(context, type_node_id, type_inst_id).type_id;
+  auto as_type = ExprAsType(context, type_node_id, type_inst_id);
 
   // If the previous node was `IdentifierNameBeforeParams`, then it would have
   // caused these entries to be pushed to the pattern stacks. But it's possible
@@ -69,10 +69,11 @@ auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
   }
 
   auto return_slot_pattern_id = AddPatternInst<SemIR::ReturnSlotPattern>(
-      context, node_id, {.type_id = type_id, .type_inst_id = type_inst_id});
+      context, node_id,
+      {.type_id = as_type.type_id, .type_inst_id = as_type.inst_id});
   auto param_pattern_id = AddPatternInst<SemIR::OutParamPattern>(
       context, node_id,
-      {.type_id = type_id,
+      {.type_id = as_type.type_id,
        .subpattern_id = return_slot_pattern_id,
        .index = SemIR::CallParamIndex::None});
   context.node_stack().Push(node_id, param_pattern_id);

+ 26 - 26
toolchain/check/handle_impl.cpp

@@ -55,14 +55,14 @@ auto HandleParseNode(Context& context, Parse::ForallId /*node_id*/) -> bool {
 
 auto HandleParseNode(Context& context, Parse::TypeImplAsId node_id) -> bool {
   auto [self_node, self_id] = context.node_stack().PopExprWithNodeId();
-  self_id = ExprAsType(context, self_node, self_id).inst_id;
-  context.node_stack().Push(node_id, self_id);
+  auto self_type_inst_id = ExprAsType(context, self_node, self_id).inst_id;
+  context.node_stack().Push(node_id, self_type_inst_id);
 
   // Introduce `Self`. Note that we add this name lexically rather than adding
   // to the `NameScopeId` of the `impl`, because this happens before we enter
   // the `impl` scope or even identify which `impl` we're declaring.
   // TODO: Revisit this once #3714 is resolved.
-  AddNameToLookup(context, SemIR::NameId::SelfType, self_id);
+  AddNameToLookup(context, SemIR::NameId::SelfType, self_type_inst_id);
   return true;
 }
 
@@ -109,11 +109,11 @@ auto HandleParseNode(Context& context, Parse::DefaultSelfImplAsId node_id)
   // is a class and found its `Self`, so additionally performing an unqualified
   // name lookup would be redundant work, but would avoid duplicating the
   // handling of the `Self` expression.
-  auto self_inst_id = AddInst(
+  auto self_inst_id = context.types().GetAsTypeInstId(AddInst(
       context, node_id,
       SemIR::NameRef{.type_id = SemIR::TypeType::SingletonTypeId,
                      .name_id = SemIR::NameId::SelfType,
-                     .value_id = context.types().GetInstId(self_type_id)});
+                     .value_id = context.types().GetInstId(self_type_id)}));
 
   // There's no need to push `Self` into scope here, because we can find it in
   // the parent class scope.
@@ -135,8 +135,8 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
                        Parse::AnyImplDeclId node_id, SemIR::ImplId impl_id,
                        Parse::NodeId self_type_node, SemIR::TypeId self_type_id,
                        Parse::NodeId params_node,
-                       SemIR::InstId constraint_inst_id,
-                       SemIR::TypeId constraint_id) -> bool {
+                       SemIR::TypeInstId constraint_type_inst_id,
+                       SemIR::TypeId constraint_type_id) -> bool {
   auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
   if (!parent_scope_id.has_value()) {
     DiagnoseExtendImplOutsideClass(context, node_id);
@@ -188,14 +188,14 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
     parent_scope.set_has_error();
   } else {
     bool is_complete = RequireCompleteType(
-        context, constraint_id, context.insts().GetLocId(constraint_inst_id),
-        [&] {
+        context, constraint_type_id,
+        context.insts().GetLocId(constraint_type_inst_id), [&] {
           CARBON_DIAGNOSTIC(ExtendImplAsIncomplete, Error,
                             "`extend impl as` incomplete facet type {0}",
                             InstIdAsType);
           return context.emitter().Build(impl.latest_decl_id(),
                                          ExtendImplAsIncomplete,
-                                         constraint_inst_id);
+                                         constraint_type_inst_id);
         });
     if (!is_complete) {
       parent_scope.set_has_error();
@@ -203,7 +203,7 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
     }
   }
 
-  parent_scope.AddExtendedScope(constraint_inst_id);
+  parent_scope.AddExtendedScope(constraint_type_inst_id);
   return true;
 }
 
@@ -318,7 +318,7 @@ static auto IsValidImplRedecl(Context& context, SemIR::Impl& new_impl,
 // and returns `None`.
 static auto CheckConstraintIsInterface(Context& context,
                                        SemIR::InstId impl_decl_id,
-                                       SemIR::InstId constraint_id)
+                                       SemIR::TypeInstId constraint_id)
     -> SemIR::SpecificInterface {
   auto facet_type_id = context.types().GetTypeIdForTypeInstId(constraint_id);
   if (facet_type_id == SemIR::ErrorInst::SingletonTypeId) {
@@ -351,15 +351,15 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
     -> std::pair<SemIR::ImplId, SemIR::InstId> {
   auto [constraint_node, constraint_id] =
       context.node_stack().PopExprWithNodeId();
-  auto [self_type_node, self_inst_id] =
+  auto [self_type_node, self_type_inst_id] =
       context.node_stack().PopWithNodeId<Parse::NodeCategory::ImplAs>();
-  auto self_type_id = context.types().GetTypeIdForTypeInstId(self_inst_id);
+  auto self_type_id = context.types().GetTypeIdForTypeInstId(self_type_inst_id);
   // Pop the `impl` introducer and any `forall` parameters as a "name".
   auto name = PopImplIntroducerAndParamsAsNameComponent(context, node_id);
   auto decl_block_id = context.inst_block_stack().Pop();
 
   // Convert the constraint expression to a type.
-  auto [constraint_inst_id, constraint_type_id] =
+  auto [constraint_type_inst_id, constraint_type_id] =
       ExprAsType(context, constraint_node, constraint_id);
 
   // Process modifiers.
@@ -385,10 +385,10 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   SemIR::Impl impl_info = {name_context.MakeEntityWithParamsBase(
                                name, impl_decl_id,
                                /*is_extern=*/false, SemIR::LibraryNameId::None),
-                           {.self_id = self_inst_id,
-                            .constraint_id = constraint_inst_id,
+                           {.self_id = self_type_inst_id,
+                            .constraint_id = constraint_type_inst_id,
                             .interface = CheckConstraintIsInterface(
-                                context, impl_decl_id, constraint_inst_id),
+                                context, impl_decl_id, constraint_type_inst_id),
                             .is_final = is_final}};
   // Add the impl declaration.
   bool invalid_redeclaration = false;
@@ -486,17 +486,17 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
       introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
     auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
     if (impl_info.generic_id.has_value()) {
-      SemIR::TypeId type_id = context.insts().Get(constraint_inst_id).type_id();
-      constraint_inst_id = AddInst<SemIR::SpecificConstant>(
-          context, context.insts().GetLocId(constraint_inst_id),
-          {.type_id = type_id,
-           .inst_id = constraint_inst_id,
-           .specific_id =
-               context.generics().GetSelfSpecific(impl_info.generic_id)});
+      constraint_type_inst_id =
+          context.types().GetAsTypeInstId(AddInst<SemIR::SpecificConstant>(
+              context, context.insts().GetLocId(constraint_type_inst_id),
+              {.type_id = SemIR::TypeType::SingletonTypeId,
+               .inst_id = constraint_type_inst_id,
+               .specific_id =
+                   context.generics().GetSelfSpecific(impl_info.generic_id)}));
     }
     if (!ExtendImpl(context, extend_node, node_id, impl_decl.impl_id,
                     self_type_node, self_type_id, name.implicit_params_loc_id,
-                    constraint_inst_id, constraint_type_id)) {
+                    constraint_type_inst_id, constraint_type_id)) {
       // Don't allow the invalid impl to be used.
       FillImplWitnessWithErrors(context, impl_info);
       context.impls().Get(impl_decl.impl_id).witness_id =

+ 1 - 1
toolchain/check/handle_where.cpp

@@ -111,7 +111,7 @@ auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
         ImplsOnNonFacetType, Error,
         "right argument of `impls` requirement must be a facet type");
     context.emitter().Emit(rhs_node, ImplsOnNonFacetType);
-    rhs_as_type.inst_id = SemIR::ErrorInst::SingletonInstId;
+    rhs_as_type.inst_id = SemIR::ErrorInst::SingletonTypeInstId;
   }
   // TODO: Require that at least one side uses a designator.
   // TODO: For things like `HashSet(.T) as type`, add an implied constraint

+ 6 - 0
toolchain/check/impl_lookup.cpp

@@ -80,6 +80,12 @@ static auto FindAssociatedImportIRs(Context& context,
           }
           break;
         }
+        case CARBON_KIND(SemIR::TypeInstId inst_id): {
+          if (inst_id.has_value()) {
+            worklist.push_back(inst_id);
+          }
+          break;
+        }
         case CARBON_KIND(SemIR::InstBlockId inst_block_id): {
           push_block(inst_block_id);
           break;

+ 1 - 1
toolchain/check/import_cpp.cpp

@@ -272,7 +272,7 @@ static auto MapType(Context& context, clang::QualType type) -> TypeExpr {
       context.ast_context().getTypeSize(type) == 32) {
     return MakeIntType(context, context.ints().Add(32));
   }
-  return {.inst_id = SemIR::ErrorInst::SingletonInstId,
+  return {.inst_id = SemIR::ErrorInst::SingletonTypeInstId,
           .type_id = SemIR::ErrorInst::SingletonTypeId};
 }
 

+ 24 - 15
toolchain/check/import_ref.cpp

@@ -699,6 +699,15 @@ static auto GetLocalConstantInstId(ImportRefResolver& resolver,
   return resolver.local_constant_values().GetInstIdIfValid(const_id);
 }
 
+// Returns the local constant InstId for an imported InstId.
+static auto GetLocalTypeInstId(ImportRefResolver& resolver,
+                               SemIR::TypeInstId inst_id) -> SemIR::TypeInstId {
+  // The input instruction is a TypeInstId, and import does not change the type
+  // of instructions, so the result is also a valid TypeInstId.
+  return SemIR::TypeInstId::UnsafeMake(
+      GetLocalConstantInstId(resolver, static_cast<SemIR::InstId>(inst_id)));
+}
+
 // Returns the ConstantId for a TypeId. Adds unresolved constants to
 // work_stack_.
 static auto GetLocalConstantId(ImportRefResolver& resolver,
@@ -1165,7 +1174,7 @@ static auto GetLocalReturnSlotPatternId(
       MakeImportedLocIdAndInst<SemIR::ReturnSlotPattern>(
           context.local_context(),
           AddImportIRInst(context, param_pattern.subpattern_id),
-          {.type_id = type_id, .type_inst_id = SemIR::InstId::None}));
+          {.type_id = type_id, .type_inst_id = SemIR::TypeInstId::None}));
   return AddInstInNoBlock(
       context.local_context(),
       MakeImportedLocIdAndInst<SemIR::OutParamPattern>(
@@ -1400,9 +1409,9 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
     return ResolveResult::Retry();
   }
 
-  auto adapted_type_inst_id =
+  auto adapted_type_inst_id = resolver.local_ir().types().GetAsTypeInstId(
       AddLoadedImportRef(resolver, SemIR::TypeType::SingletonTypeId,
-                         inst.adapted_type_inst_id, adapted_type_const_id);
+                         inst.adapted_type_inst_id, adapted_type_const_id));
 
   // Create a corresponding instruction to represent the declaration.
   auto inst_id = AddInstInNoBlock(
@@ -1418,7 +1427,7 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
                                 SemIR::ArrayType inst) -> ResolveResult {
   CARBON_CHECK(inst.type_id == SemIR::TypeType::SingletonTypeId);
   auto element_type_inst_id =
-      GetLocalConstantInstId(resolver, inst.element_type_inst_id);
+      GetLocalTypeInstId(resolver, inst.element_type_inst_id);
   auto bound_id = GetLocalConstantInstId(resolver, inst.bound_id);
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
@@ -1559,9 +1568,9 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
     return ResolveResult::Retry();
   }
 
-  auto base_type_inst_id =
+  auto base_type_inst_id = resolver.local_ir().types().GetAsTypeInstId(
       AddLoadedImportRef(resolver, SemIR::TypeType::SingletonTypeId,
-                         inst.base_type_inst_id, base_type_const_id);
+                         inst.base_type_inst_id, base_type_const_id));
 
   // Create a corresponding instruction to represent the declaration.
   auto inst_id = AddInstInNoBlock(
@@ -2130,8 +2139,8 @@ static auto MakeImplDeclaration(ImportContext& context,
           AddImportIRInst(context, import_impl.latest_decl_id()), impl_decl));
   impl_decl.impl_id = context.local_impls().Add(
       {GetIncompleteLocalEntityBase(context, impl_decl_id, import_impl),
-       {.self_id = SemIR::InstId::None,
-        .constraint_id = SemIR::InstId::None,
+       {.self_id = SemIR::TypeInstId::None,
+        .constraint_id = SemIR::TypeInstId::None,
         .interface = SemIR::SpecificInterface::None,
         .witness_id = witness_id}});
 
@@ -2226,12 +2235,12 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
 
   // Create instructions for self and constraint to hold the symbolic constant
   // value for a generic impl.
-  new_impl.self_id =
+  new_impl.self_id = resolver.local_ir().types().GetAsTypeInstId(
       AddLoadedImportRef(resolver, SemIR::TypeType::SingletonTypeId,
-                         import_impl.self_id, self_const_id);
-  new_impl.constraint_id =
+                         import_impl.self_id, self_const_id));
+  new_impl.constraint_id = resolver.local_ir().types().GetAsTypeInstId(
       AddLoadedImportRef(resolver, SemIR::TypeType::SingletonTypeId,
-                         import_impl.constraint_id, constraint_const_id);
+                         import_impl.constraint_id, constraint_const_id));
   new_impl.interface = GetLocalSpecificInterface(
       resolver, import_impl.interface, specific_interface_data);
   if (import_impl.is_complete()) {
@@ -2499,7 +2508,7 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
 static auto TryResolveTypedInst(ImportRefResolver& resolver,
                                 SemIR::FacetValue inst) -> ResolveResult {
   auto type_id = GetLocalConstantId(resolver, inst.type_id);
-  auto type_inst_id = GetLocalConstantInstId(resolver, inst.type_inst_id);
+  auto type_inst_id = GetLocalTypeInstId(resolver, inst.type_inst_id);
   auto witnesses = GetLocalInstBlockContents(resolver, inst.witnesses_block_id);
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
@@ -2808,9 +2817,9 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
     -> ResolveResult {
   CARBON_CHECK(inst.type_id == SemIR::TypeType::SingletonTypeId);
   auto class_const_inst_id =
-      GetLocalConstantInstId(resolver, inst.class_type_inst_id);
+      GetLocalTypeInstId(resolver, inst.class_type_inst_id);
   auto elem_const_inst_id =
-      GetLocalConstantInstId(resolver, inst.element_type_inst_id);
+      GetLocalTypeInstId(resolver, inst.element_type_inst_id);
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
   }

+ 1 - 1
toolchain/check/node_stack.h

@@ -384,7 +384,7 @@ class NodeStack {
         Parse::NodeCategory::MemberName | Parse::NodeCategory::NonExprName,
         Id::KindFor<SemIR::NameId>());
     set_id_if_category_is(Parse::NodeCategory::ImplAs,
-                          Id::KindFor<SemIR::InstId>());
+                          Id::KindFor<SemIR::TypeInstId>());
     set_id_if_category_is(Parse::NodeCategory::Decl |
                               Parse::NodeCategory::Statement |
                               Parse::NodeCategory::Modifier,

+ 12 - 0
toolchain/check/subst.cpp

@@ -89,6 +89,12 @@ static auto PushOperand(Context& context, Worklist& worklist,
       }
       break;
     }
+    case CARBON_KIND(SemIR::TypeInstId inst_id): {
+      if (inst_id.has_value()) {
+        worklist.Push(inst_id);
+      }
+      break;
+    }
     case CARBON_KIND(SemIR::InstBlockId inst_block_id): {
       push_block(inst_block_id);
       break;
@@ -175,6 +181,12 @@ static auto PopOperand(Context& context, Worklist& worklist,
       }
       return worklist.Pop().index;
     }
+    case CARBON_KIND(SemIR::TypeInstId inst_id): {
+      if (!inst_id.has_value()) {
+        return arg.value();
+      }
+      return worklist.Pop().index;
+    }
     case CARBON_KIND(SemIR::InstBlockId inst_block_id): {
       return pop_block_id(inst_block_id).index;
     }

+ 2 - 2
toolchain/check/type.cpp

@@ -157,8 +157,8 @@ auto GetPointerType(Context& context, SemIR::InstId pointee_type_id)
   return GetTypeImpl<SemIR::PointerType>(context, pointee_type_id);
 }
 
-auto GetUnboundElementType(Context& context, SemIR::InstId class_type_id,
-                           SemIR::InstId element_type_id) -> SemIR::TypeId {
+auto GetUnboundElementType(Context& context, SemIR::TypeInstId class_type_id,
+                           SemIR::TypeInstId element_type_id) -> SemIR::TypeId {
   return GetTypeImpl<SemIR::UnboundElementType>(context, class_type_id,
                                                 element_type_id);
 }

+ 2 - 2
toolchain/check/type.h

@@ -83,8 +83,8 @@ auto GetTupleType(Context& context, llvm::ArrayRef<SemIR::InstId> type_inst_ids)
     -> SemIR::TypeId;
 
 // Returns an unbound element type.
-auto GetUnboundElementType(Context& context, SemIR::InstId class_type_id,
-                           SemIR::InstId element_type_id) -> SemIR::TypeId;
+auto GetUnboundElementType(Context& context, SemIR::TypeInstId class_type_id,
+                           SemIR::TypeInstId element_type_id) -> SemIR::TypeId;
 
 }  // namespace Carbon::Check
 

+ 2 - 2
toolchain/driver/testdata/compile/raw_and_textual_ir.carbon

@@ -63,12 +63,12 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:     inst25:          {kind: Converted, arg0: inst21, arg1: inst15, type: type(TypeType)}
 // CHECK:STDOUT:     inst26:          {kind: Converted, arg0: inst22, arg1: inst15, type: type(TypeType)}
 // CHECK:STDOUT:     inst27:          {kind: Converted, arg0: inst24, arg1: inst23, type: type(TypeType)}
-// CHECK:STDOUT:     inst28:          {kind: ReturnSlotPattern, arg0: inst24, type: type(inst23)}
+// CHECK:STDOUT:     inst28:          {kind: ReturnSlotPattern, arg0: inst27, type: type(inst23)}
 // CHECK:STDOUT:     inst29:          {kind: OutParamPattern, arg0: inst28, arg1: call_param1, type: type(inst23)}
 // CHECK:STDOUT:     inst30:          {kind: ValueParam, arg0: call_param0, arg1: name1, type: type(inst15)}
 // CHECK:STDOUT:     inst31:          {kind: SpliceBlock, arg0: inst_block4, arg1: inst17, type: type(TypeType)}
 // CHECK:STDOUT:     inst32:          {kind: OutParam, arg0: call_param1, arg1: name(ReturnSlot), type: type(inst23)}
-// CHECK:STDOUT:     inst33:          {kind: ReturnSlot, arg0: inst24, arg1: inst32, type: type(inst23)}
+// CHECK:STDOUT:     inst33:          {kind: ReturnSlot, arg0: inst27, arg1: inst32, type: type(inst23)}
 // CHECK:STDOUT:     inst34:          {kind: FunctionDecl, arg0: function0, arg1: inst_block11, type: type(inst35)}
 // CHECK:STDOUT:     inst35:          {kind: FunctionType, arg0: function0, arg1: specific<none>, type: type(TypeType)}
 // CHECK:STDOUT:     inst36:          {kind: StructValue, arg0: inst_block_empty, type: type(inst35)}

+ 2 - 2
toolchain/driver/testdata/compile/raw_ir.carbon

@@ -77,11 +77,11 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:     inst28:          {kind: Converted, arg0: inst25, arg1: inst24, type: type(TypeType)}
 // CHECK:STDOUT:     inst29:          {kind: TupleType, arg0: inst_block11, type: type(TypeType)}
 // CHECK:STDOUT:     inst30:          {kind: Converted, arg0: inst27, arg1: inst29, type: type(TypeType)}
-// CHECK:STDOUT:     inst31:          {kind: ReturnSlotPattern, arg0: inst27, type: type(symbolic_constant5)}
+// CHECK:STDOUT:     inst31:          {kind: ReturnSlotPattern, arg0: inst30, type: type(symbolic_constant5)}
 // CHECK:STDOUT:     inst32:          {kind: OutParamPattern, arg0: inst31, arg1: call_param1, type: type(symbolic_constant5)}
 // CHECK:STDOUT:     inst33:          {kind: ValueParam, arg0: call_param0, arg1: name2, type: type(symbolic_constant3)}
 // CHECK:STDOUT:     inst34:          {kind: OutParam, arg0: call_param1, arg1: name(ReturnSlot), type: type(symbolic_constant5)}
-// CHECK:STDOUT:     inst35:          {kind: ReturnSlot, arg0: inst27, arg1: inst34, type: type(symbolic_constant5)}
+// CHECK:STDOUT:     inst35:          {kind: ReturnSlot, arg0: inst30, arg1: inst34, type: type(symbolic_constant5)}
 // CHECK:STDOUT:     inst36:          {kind: FunctionDecl, arg0: function0, arg1: inst_block14, type: type(inst40)}
 // CHECK:STDOUT:     inst37:          {kind: BindSymbolicName, arg0: entity_name0, arg1: inst<none>, type: type(TypeType)}
 // CHECK:STDOUT:     inst38:          {kind: SymbolicBindingPattern, arg0: entity_name0, type: type(TypeType)}

+ 4 - 0
toolchain/sem_ir/formatter.cpp

@@ -1398,6 +1398,10 @@ class FormatterImpl {
     }
   }
 
+  auto FormatName(TypeInstId id) -> void {
+    FormatName(static_cast<InstId>(id));
+  }
+
   auto FormatLabel(InstBlockId id) -> void {
     out_ << inst_namer_->GetLabelFor(scope_, id);
   }

+ 2 - 1
toolchain/sem_ir/id_kind.h

@@ -179,7 +179,8 @@ using IdKind = TypeEnum<
     NameScopeId,
     SpecificId,
     SpecificInterfaceId,
-    StructTypeFieldsId>;
+    StructTypeFieldsId,
+    TypeInstId>;
 // clang-format on
 
 }  // namespace Carbon::SemIR

+ 20 - 0
toolchain/sem_ir/ids.h

@@ -52,6 +52,26 @@ struct InstId : public IdBase<InstId> {
 
 constexpr InstId InstId::InitTombstone = InstId(NoneIndex - 1);
 
+// And InstId whose value is a type. The fact it's a type is CHECKed on
+// construction, and this allows that check to be represented in the type
+// system.
+struct TypeInstId : public InstId {
+  static const TypeInstId None;
+
+  using InstId::InstId;
+
+  static constexpr auto UnsafeMake(InstId id) -> TypeInstId {
+    return TypeInstId(UnsafeCtor(), id);
+  }
+
+ private:
+  struct UnsafeCtor {};
+  explicit constexpr TypeInstId(UnsafeCtor /*unsafe*/, InstId id)
+      : InstId(id) {}
+};
+
+constexpr TypeInstId TypeInstId::None = TypeInstId::UnsafeMake(InstId::None);
+
 // An ID of an instruction that is referenced absolutely by another instruction.
 // This should only be used as the type of a field within a typed instruction
 // class.

+ 2 - 2
toolchain/sem_ir/impl.h

@@ -17,9 +17,9 @@ struct ImplFields {
   // This following members always have values and do not change.
 
   // The type for which the impl is implementing a constraint.
-  InstId self_id;
+  TypeInstId self_id;
   // The constraint that the impl implements.
-  InstId constraint_id;
+  TypeInstId constraint_id;
 
   // The single interface to implement from `constraint_id`.
   // The members are `None` if `constraint_id` isn't complete or doesn't

+ 31 - 14
toolchain/sem_ir/type.cpp

@@ -8,28 +8,45 @@
 
 namespace Carbon::SemIR {
 
-auto TypeStore::GetTypeIdForTypeConstantId(SemIR::ConstantId constant_id) const
-    -> SemIR::TypeId {
+// Verify that the constant value's type is `TypeType` (or an error).
+static void CheckTypeOfConstantIsTypeType(File& file, ConstantId constant_id) {
   CARBON_CHECK(constant_id.is_constant(),
                "Canonicalizing non-constant type: {0}", constant_id);
-  auto type_id = file_->insts()
-                     .Get(file_->constant_values().GetInstId(constant_id))
-                     .type_id();
-  CARBON_CHECK(type_id == SemIR::TypeType::SingletonTypeId ||
-                   constant_id == SemIR::ErrorInst::SingletonConstantId,
+  auto type_id =
+      file.insts().Get(file.constant_values().GetInstId(constant_id)).type_id();
+  CARBON_CHECK(type_id == TypeType::SingletonTypeId ||
+                   constant_id == ErrorInst::SingletonConstantId,
                "Forming type ID for non-type constant of type {0}",
-               GetAsInst(type_id));
+               file.types().GetAsInst(type_id));
+}
+
+auto TypeStore::GetTypeIdForTypeConstantId(ConstantId constant_id) const
+    -> TypeId {
+  CheckTypeOfConstantIsTypeType(*file_, constant_id);
+  return TypeId::ForTypeConstant(constant_id);
+}
+
+auto TypeStore::GetTypeIdForTypeInstId(InstId inst_id) const -> TypeId {
+  auto constant_id = file_->constant_values().Get(inst_id);
+  CheckTypeOfConstantIsTypeType(*file_, constant_id);
+  return TypeId::ForTypeConstant(constant_id);
+}
 
-  return SemIR::TypeId::ForTypeConstant(constant_id);
+auto TypeStore::GetTypeIdForTypeInstId(TypeInstId inst_id) const -> TypeId {
+  auto constant_id = file_->constant_values().Get(inst_id);
+  return TypeId::ForTypeConstant(constant_id);
 }
 
-auto TypeStore::GetTypeIdForTypeInstId(SemIR::InstId inst_id) const
-    -> SemIR::TypeId {
-  return GetTypeIdForTypeConstantId(file_->constant_values().Get(inst_id));
+auto TypeStore::GetAsTypeInstId(InstId inst_id) const -> TypeInstId {
+  auto constant_id = file_->constant_values().Get(inst_id);
+  CheckTypeOfConstantIsTypeType(*file_, constant_id);
+  return TypeInstId::UnsafeMake(inst_id);
 }
 
-auto TypeStore::GetInstId(TypeId type_id) const -> InstId {
-  return file_->constant_values().GetInstId(GetConstantId(type_id));
+auto TypeStore::GetInstId(TypeId type_id) const -> TypeInstId {
+  // The instruction for a TypeId has a value of that TypeId.
+  return TypeInstId::UnsafeMake(
+      file_->constant_values().GetInstId(GetConstantId(type_id)));
 }
 
 auto TypeStore::GetAsInst(TypeId type_id) const -> Inst {

+ 9 - 4
toolchain/sem_ir/type.h

@@ -39,8 +39,7 @@ class TypeStore : public Yaml::Printable<TypeStore> {
   // Facet values are of the same typishness as types, but are not themselves
   // types, so they can not be passed here. They should be converted to a type
   // through an `as type` conversion, that is, to a value of type `TypeType`.
-  auto GetTypeIdForTypeConstantId(SemIR::ConstantId constant_id) const
-      -> SemIR::TypeId;
+  auto GetTypeIdForTypeConstantId(ConstantId constant_id) const -> TypeId;
 
   // Returns the type ID for an instruction whose constant value is a type
   // value, i.e. it is a value of type `TypeType`.
@@ -50,10 +49,16 @@ class TypeStore : public Yaml::Printable<TypeStore> {
   // so they can not be passed here. They should be converted to a type through
   // an `as type` conversion, such as to a `FacetAccessType` instruction whose
   // value is of type `TypeType`.
-  auto GetTypeIdForTypeInstId(SemIR::InstId inst_id) const -> SemIR::TypeId;
+  auto GetTypeIdForTypeInstId(InstId inst_id) const -> TypeId;
+  auto GetTypeIdForTypeInstId(TypeInstId inst_id) const -> TypeId;
+
+  // Converts an `InstId` to a `TypeInstId` of the same id value. This process
+  // involves checking that the type of the instruction's value is `TypeType`,
+  // and then this check is encoded in the type system via `TypeInstId`.
+  auto GetAsTypeInstId(InstId inst_id) const -> TypeInstId;
 
   // Returns the ID of the instruction used to define the specified type.
-  auto GetInstId(TypeId type_id) const -> InstId;
+  auto GetInstId(TypeId type_id) const -> TypeInstId;
 
   // Returns the instruction used to define the specified type.
   auto GetAsInst(TypeId type_id) const -> Inst;

+ 11 - 9
toolchain/sem_ir/typed_insts.h

@@ -81,7 +81,7 @@ struct AnyFoundationDecl {
   static constexpr InstKind Kinds[] = {InstKind::AdaptDecl, InstKind::BaseDecl};
 
   InstKind kind;
-  InstId foundation_type_inst_id;
+  TypeInstId foundation_type_inst_id;
   // Kind-specific data.
   AnyRawId arg1;
 };
@@ -94,7 +94,7 @@ struct AdaptDecl {
        .is_lowered = false});
 
   // No type_id; this is not a value.
-  InstId adapted_type_inst_id;
+  TypeInstId adapted_type_inst_id;
 };
 
 // Takes the address of a reference expression, such as for the `&` address-of
@@ -193,7 +193,7 @@ struct ArrayType {
 
   TypeId type_id;
   InstId bound_id;
-  InstId element_type_inst_id;
+  TypeInstId element_type_inst_id;
 };
 
 // Perform a no-op conversion to a compatible type.
@@ -287,7 +287,7 @@ struct BaseDecl {
       {.ir_name = "base_decl", .constant_kind = InstConstantKind::Unique});
 
   TypeId type_id;
-  InstId base_type_inst_id;
+  TypeInstId base_type_inst_id;
   ElementIndex index;
 };
 
@@ -665,6 +665,8 @@ struct ErrorInst {
       ConstantId::ForConcreteConstant(SingletonInstId);
   static constexpr auto SingletonTypeId =
       TypeId::ForTypeConstant(SingletonConstantId);
+  static constexpr auto SingletonTypeInstId =
+      TypeInstId::UnsafeMake(SingletonInstId);
 
   TypeId type_id;
 };
@@ -736,7 +738,7 @@ struct FacetValue {
   // A `FacetType`.
   TypeId type_id;
   // The type that you will get if you cast this value to `type`.
-  InstId type_inst_id;
+  TypeInstId type_inst_id;
   // The set of `ImplWitness` instructions for a `FacetType`. The witnesses are
   // in the same order as the set of `required_interfaces` in the
   // `IdentifiedFacetType` of the `FacetType` from `type_id`, so that an index
@@ -1387,7 +1389,7 @@ struct ReturnSlot {
   // The function return type as originally written by the user. For diagnostics
   // only; this has no semantic significance, and is not preserved across
   // imports.
-  InstId type_inst_id;
+  TypeInstId type_inst_id;
 
   // The storage that will be initialized by the function.
   InstId storage_id;
@@ -1410,7 +1412,7 @@ struct ReturnSlotPattern {
   // The function return type as originally written by the user. For diagnostics
   // only; this has no semantic significance, and is not preserved across
   // imports.
-  InstId type_inst_id;
+  TypeInstId type_inst_id;
 };
 
 // An `expr == expr` clause in a `where` expression or `require` declaration.
@@ -1782,9 +1784,9 @@ struct UnboundElementType {
 
   TypeId type_id;
   // The `ClassType` that a value of this type is an element of.
-  InstId class_type_inst_id;
+  TypeInstId class_type_inst_id;
   // The type of the element.
-  InstId element_type_inst_id;
+  TypeInstId element_type_inst_id;
 };
 
 // Converts from a value expression to an ephemeral reference expression, in