Track rewrites in IdentifiedFacetType (#7866)

This allows us to collect rewrites from named constraints and use them
to initialize the witness table for an `impl as` statement.

Only rewrites from extend constraints are tracked, as other constraints
should turn into equality constraints, as they don't modify the witness
table.
This commit is contained in:
Dana Jansens
2026-09-30 18:07:18 +00:00
committed by GitHub
parent c1492aab75
commit bb199f6226
9 changed files with 80 additions and 104 deletions
+12 -4
View File
@@ -250,12 +250,19 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
// This requires that the facet type is identified, and returns the single
// interface from the identified facet type. It returns None if an error was
// diagnosed.
auto specific_interface = CheckConstraintIsInterface(
auto identified_id = CheckConstraintIsInterface(
context, node_id, self_type_inst_id, full_constraint_type_inst_id);
if (!specific_interface.interface_id.has_value()) {
if (!identified_id.has_value()) {
full_constraint_type_inst_id = SemIR::ErrorInst::TypeInstId;
}
auto specific_interface =
full_constraint_type_inst_id != SemIR::ErrorInst::InstId
? context.identified_facet_types()
.Get(identified_id)
.impl_as_target_interface()
: SemIR::SpecificInterface::None;
// Store an instruction in the decl's eval block that contains the target
// interface's specific, whose constant value will be updated when specifics
// are applied to the impl.
@@ -268,7 +275,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
// we introduced a new instruction with a SpecificId operand instead of
// reusing ImplSelfWitness for this.
auto interface_inst_id =
specific_interface.interface_id.has_value()
full_constraint_type_inst_id != SemIR::ErrorInst::InstId
? AddInst<SemIR::ImplSelfWitness>(
context, node_id,
{.type_id =
@@ -407,7 +414,8 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
// also must be part of the generic eval block by coming before
// FinishGenericDecl().
impl.witness_id = AddImplWitnessForDeclaration(
context, node_id, impl, full_constraint_type_inst_id,
context, node_id, SemIR::LocId(full_constraint_type_inst_id),
impl, identified_id,
context.generics().GetSelfSpecific(impl.generic_id));
impl.witness_block_id = context.inst_block_stack().Pop();
+17 -36
View File
@@ -395,32 +395,14 @@ auto AddImpl(Context& context, const SemIR::Impl& impl,
}
auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
SemIR::LocId constraint_loc_id,
const SemIR::Impl& impl,
SemIR::TypeInstId full_constraint_id,
SemIR::IdentifiedFacetTypeId identified_id,
SemIR::SpecificId self_specific_id)
-> SemIR::InstId {
auto facet_type_id =
context.types().GetTypeIdForTypeInstId(full_constraint_id);
CARBON_CHECK(facet_type_id != SemIR::ErrorInst::TypeId);
auto facet_type = context.types().GetAs<SemIR::FacetType>(facet_type_id);
// TODO: We need to collect rewrites from named constraints too, so we will
// want to get them from the IdentifiedFacetType, or something similar. That
// process should also replace `.Self` in the constraints as appropriate.
const auto& declared_facet_type =
context.declared_facet_types().Get(facet_type.declared_facet_type_id);
const auto& identified = context.identified_facet_types().Get(identified_id);
// An iterator over the rewrite_constraints where the LHS of the rewrite names
// a member of the `impl.interface`. This filters out rewrites of names
// from other interfaces, as they do not set values in the witness table.
auto rewrites_into_interface_to_witness = llvm::make_filter_range(
declared_facet_type.rewrite_constraints,
[&](const SemIR::DeclaredFacetType::RewriteConstraint& rewrite) {
auto access = context.insts().GetAs<SemIR::ImplWitnessAccess>(
GetImplWitnessAccessWithoutSubstitution(context, rewrite.lhs_id));
return context.insts().Is<SemIR::ImplSelfWitness>(access.witness_id);
});
if (rewrites_into_interface_to_witness.empty()) {
if (identified.rewrites().empty()) {
// The witness table is not needed until the definition. Make a placeholder
// for the declaration.
auto witness_table_inst_id = AddInst<SemIR::ImplWitnessTable>(
@@ -472,9 +454,9 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
.specific_id = self_specific_id});
}
for (auto rewrite : rewrites_into_interface_to_witness) {
auto access = context.insts().GetAs<SemIR::ImplWitnessAccess>(
GetImplWitnessAccessWithoutSubstitution(context, rewrite.lhs_id));
for (auto rewrite : identified.rewrites()) {
auto access = context.constant_values().GetInstAs<SemIR::ImplWitnessAccess>(
rewrite.lhs);
auto& table_entry = table[access.index.index];
if (table_entry == SemIR::ErrorInst::InstId) {
// Don't overwrite an error value. This prioritizes not generating
@@ -482,7 +464,7 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
// for it to use to attempt recovery.
continue;
}
auto rewrite_inst_id = rewrite.rhs_id;
auto rewrite_inst_id = context.constant_values().GetInstId(rewrite.rhs);
if (rewrite_inst_id == SemIR::ErrorInst::InstId) {
table_entry = SemIR::ErrorInst::InstId;
continue;
@@ -513,7 +495,7 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
CARBON_DIAGNOSTIC(RewriteForAssociatedFunction, Error,
"rewrite specified for associated function {0}",
SemIR::NameId);
context.emitter().Emit(full_constraint_id, RewriteForAssociatedFunction,
context.emitter().Emit(constraint_loc_id, RewriteForAssociatedFunction,
fn.name_id);
table_entry = SemIR::ErrorInst::InstId;
continue;
@@ -540,9 +522,8 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
// Perform the conversion of the value to the type. We skipped this when
// forming the facet type because the type of the associated constant
// was symbolic.
auto converted_inst_id =
ConvertToValueOfType(context, SemIR::LocId(full_constraint_id),
rewrite_inst_id, assoc_const_type_id);
auto converted_inst_id = ConvertToValueOfType(
context, constraint_loc_id, rewrite_inst_id, assoc_const_type_id);
// Canonicalize the converted constant value.
converted_inst_id =
context.constant_values().GetConstantInstId(converted_inst_id);
@@ -559,7 +540,7 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
"after conversion to {2}",
SemIR::NameId, InstIdAsConstant, SemIR::TypeId);
context.emitter().Emit(
full_constraint_id, AssociatedConstantNotConstantAfterConversion,
constraint_loc_id, AssociatedConstantNotConstantAfterConversion,
assoc_const.name_id, rewrite_inst_id, assoc_const_type_id);
rewrite_inst_id = SemIR::ErrorInst::InstId;
}
@@ -1022,11 +1003,11 @@ auto CheckConstraintIsFacetType(Context& context, SemIR::LocId loc_id,
auto CheckConstraintIsInterface(Context& context, SemIR::LocId loc_id,
SemIR::InstId self_id,
SemIR::TypeInstId constraint_id)
-> SemIR::SpecificInterface {
-> SemIR::IdentifiedFacetTypeId {
auto canon_constraint_id =
context.constant_values().GetConstantTypeInstId(constraint_id);
if (canon_constraint_id == SemIR::ErrorInst::TypeInstId) {
return SemIR::SpecificInterface::None;
return SemIR::IdentifiedFacetTypeId::None;
}
auto identified_id = RequireIdentifiedFacetType(
context, SemIR::LocId(constraint_id),
@@ -1038,7 +1019,7 @@ auto CheckConstraintIsInterface(Context& context, SemIR::LocId loc_id,
builder.Context(loc_id, ImplOfUnidentifiedFacetType, constraint_id);
});
if (!identified_id.has_value()) {
return SemIR::SpecificInterface::None;
return SemIR::IdentifiedFacetTypeId::None;
}
const auto& identified = context.identified_facet_types().Get(identified_id);
if (!identified.is_valid_impl_as_target()) {
@@ -1046,9 +1027,9 @@ auto CheckConstraintIsInterface(Context& context, SemIR::LocId loc_id,
"impl as {0} interfaces, expected 1", int);
context.emitter().Emit(loc_id, ImplOfNotOneInterface,
identified.num_interfaces_to_impl());
return SemIR::SpecificInterface::None;
return SemIR::IdentifiedFacetTypeId::None;
}
return identified.impl_as_target_interface();
return identified_id;
}
auto GetImplInterfaceInSpecific(Context& context, const SemIR::Impl& impl,
+6 -4
View File
@@ -52,8 +52,9 @@ auto AddImpl(Context& context, const SemIR::Impl& impl,
// constraint facet type. `self_specific_id` will be the `specific_id` of the
// resulting witness.
auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
SemIR::LocId constraint_loc_id,
const SemIR::Impl& impl,
SemIR::TypeInstId full_constraint_id,
SemIR::IdentifiedFacetTypeId identified_id,
SemIR::SpecificId self_specific_id)
-> SemIR::InstId;
@@ -93,12 +94,13 @@ auto CheckConstraintIsFacetType(Context& context, SemIR::LocId loc_id,
SemIR::TypeInstId constraint_id) -> bool;
// Checks that the constraint specified for the impl is a valid, identified
// facet type that extends a single interface. Returns the interface that the
// impl implements. On error, issues a diagnostic and returns `None`.
// facet type that extends a single interface. Returns the IdentifiedFacetType
// which contains the interface that the impl implements. On error, issues a
// diagnostic and returns `None`.
auto CheckConstraintIsInterface(Context& context, SemIR::LocId loc_id,
SemIR::InstId self_id,
SemIR::TypeInstId constraint_id)
-> SemIR::SpecificInterface;
-> SemIR::IdentifiedFacetTypeId;
// Given a specific for the impl, returns the specific interface that the impl
// declaration is implementing. Returns None in the case of an error being
+3
View File
@@ -471,6 +471,9 @@ static auto VerifyQueryFacetTypeConstraints(
SemIR::ConstantId query_facet_type_const_id,
llvm::ArrayRef<SemIR::IdentifiedFacetType::RequiredImpl> req_impls,
llvm::ArrayRef<SemIR::InstId> witness_inst_ids) -> bool {
// TODO: Get the rewrites and equality constraints from the
// IdentifiedFacetType so that we also find and verify requirements from named
// constraints.
const auto& declared_facet_type = context.declared_facet_types().Get(
context.constant_values()
.GetInstAs<SemIR::FacetType>(query_facet_type_const_id)
@@ -227,7 +227,7 @@ fn G() {
F(C);
}
// --- fail_todo_impl_as_constraint_containing_rewrite_for_concrete_interface.carbon
// --- impl_as_constraint_containing_rewrite_for_concrete_interface.carbon
library "[[@TEST_NAME]]";
interface I {
@@ -239,28 +239,14 @@ constraint N {
}
class C;
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_concrete_interface.carbon:[[@LINE+7]]:1: error: associated constant I1 not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: impl C as N {}
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_concrete_interface.carbon:[[@LINE-11]]:7: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: let I1: type;
// CHECK:STDERR: ^~~~~~~~
// CHECK:STDERR:
impl C as N {}
fn F(generic _: I where .I1 = .Self) {}
fn G() {
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_concrete_interface.carbon:[[@LINE+7]]:3: error: cannot convert type `C` into type implementing `I where .(I.I1) = .Self` [ConversionFailureTypeToFacet]
// CHECK:STDERR: F(C);
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_concrete_interface.carbon:[[@LINE-5]]:14: note: initializing generic parameter `_` declared here [InitializingGenericParam]
// CHECK:STDERR: fn F(generic _: I where .I1 = .Self) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
F(C);
}
// --- fail_todo_impl_as_constraint_containing_rewrite_for_generic_interface.carbon
// --- impl_as_constraint_containing_rewrite_for_generic_interface.carbon
library "[[@TEST_NAME]]";
interface I(T: type) {
@@ -272,24 +258,10 @@ constraint N {
}
class C;
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_generic_interface.carbon:[[@LINE+7]]:1: error: associated constant I1 not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: impl C as N {}
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_generic_interface.carbon:[[@LINE-11]]:7: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: let I1: type;
// CHECK:STDERR: ^~~~~~~~
// CHECK:STDERR:
impl C as N {}
fn F(generic _: I(.Self) where .I1 = .Self) {}
fn G() {
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_generic_interface.carbon:[[@LINE+7]]:3: error: cannot convert type `C` into type implementing `I(.Self) where .(I(.Self).I1) = .Self` [ConversionFailureTypeToFacet]
// CHECK:STDERR: F(C);
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_todo_impl_as_constraint_containing_rewrite_for_generic_interface.carbon:[[@LINE-5]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn F(generic _: I(.Self) where .I1 = .Self) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
F(C);
}
+2 -26
View File
@@ -59,7 +59,7 @@ interface J {}
// CHECK:STDERR:
impl forall [T: type] T as I where .X impls J and .X = () {}
// --- fail_todo_rewrite_in_named_constraint.carbon
// --- rewrite_in_named_constraint.carbon
library "[[@TEST_NAME]]";
interface I { let X: type; }
@@ -72,13 +72,6 @@ constraint GivesI {
impl () as J {}
// The rewrite from `extend` is inherited and should satisfy that `.X impls J`.
// CHECK:STDERR: fail_todo_rewrite_in_named_constraint.carbon:[[@LINE+7]]:1: error: associated constant X not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: impl forall [T: type] T as GivesI where .X impls J {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_rewrite_in_named_constraint.carbon:[[@LINE-13]]:19: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: interface I { let X: type; }
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR:
impl forall [T: type] T as GivesI where .X impls J {}
// --- fail_rewrite_in_named_constraint_missing_impl.carbon
@@ -92,12 +85,9 @@ constraint GivesI {
}
// .X does not impl J.
// CHECK:STDERR: fail_rewrite_in_named_constraint_missing_impl.carbon:[[@LINE+7]]:1: error: associated constant X not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: fail_rewrite_in_named_constraint_missing_impl.carbon:[[@LINE+4]]:1: error: constraint `GivesI where .(I.X) impls J` being implemented requires that `()` implements `J` [IdentifiedRequireImplsNotImplemented]
// CHECK:STDERR: impl forall [T: type] T as GivesI where .X impls J {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_rewrite_in_named_constraint_missing_impl.carbon:[[@LINE-11]]:19: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: interface I { let X: type; }
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR:
impl forall [T: type] T as GivesI where .X impls J {}
@@ -115,13 +105,6 @@ constraint GivesI {
impl () as J {}
// The rewrite from `extend` is inherited and should satisfy that `.X impls J`.
// CHECK:STDERR: fail_todo_rewrite_and_impls_in_named_constraint.carbon:[[@LINE+11]]:1: error: associated constant X not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: impl forall [T: type] T as GivesI {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_rewrite_and_impls_in_named_constraint.carbon:[[@LINE-14]]:19: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: interface I { let X: type; }
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_rewrite_and_impls_in_named_constraint.carbon:[[@LINE+4]]:1: error: constraint `GivesI` being implemented requires that `T.(I.X)` implements `J` [IdentifiedRequireImplsNotImplemented]
// CHECK:STDERR: impl forall [T: type] T as GivesI {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -140,13 +123,6 @@ constraint GivesI {
}
// .X does not impl J.
// CHECK:STDERR: fail_rewrite_and_impls_in_named_constraint_missing_impl.carbon:[[@LINE+11]]:1: error: associated constant X not given a value in impl of interface I [ImplAssociatedConstantNeedsValue]
// CHECK:STDERR: impl forall [T: type] T as GivesI {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_rewrite_and_impls_in_named_constraint_missing_impl.carbon:[[@LINE-12]]:19: note: associated constant declared here [AssociatedConstantHere]
// CHECK:STDERR: interface I { let X: type; }
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_rewrite_and_impls_in_named_constraint_missing_impl.carbon:[[@LINE+4]]:1: error: constraint `GivesI` being implemented requires that `T.(I.X)` implements `J` [IdentifiedRequireImplsNotImplemented]
// CHECK:STDERR: impl forall [T: type] T as GivesI {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+17 -1
View File
@@ -8,6 +8,7 @@
#include "llvm/ADT/SmallVector.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/cpp/import.h"
#include "toolchain/check/facet_type.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/literal.h"
@@ -1012,6 +1013,7 @@ static auto IdentifyFacetType(Context& context, SemIR::LocId loc_id,
bool partially_identified = false;
llvm::SmallVector<SemIR::IdentifiedFacetType::RequiredImpl> extends;
llvm::SmallVector<SemIR::IdentifiedFacetType::RequiredImpl> impls;
llvm::SmallVector<SemIR::IdentifiedFacetType::Rewrite> rewrites;
while (!work.empty()) {
SelfImplsFacetType next_impls = work.pop_back_val();
@@ -1046,6 +1048,20 @@ static auto IdentifyFacetType(Context& context, SemIR::LocId loc_id,
llvm::append_range(
impls, llvm::map_range(declared_facet_type.type_impls_interfaces,
type_and_interface));
if (facet_type_extends) {
auto rewrite_as_constants =
[&](auto rewrite) -> SemIR::IdentifiedFacetType::Rewrite {
return {context.constant_values().Get(
GetImplWitnessAccessWithoutSubstitution(context,
rewrite.lhs_id)),
context.constant_values().Get(rewrite.rhs_id)};
};
llvm::append_range(
rewrites, llvm::map_range(declared_facet_type.rewrite_constraints,
rewrite_as_constants));
} else {
// TODO: Store the rewrites as equality constraints.
}
if (declared_facet_type.extend_named_constraints.empty() &&
declared_facet_type.self_impls_named_constraints.empty() &&
@@ -1229,7 +1245,7 @@ static auto IdentifyFacetType(Context& context, SemIR::LocId loc_id,
// TODO: Process other kinds of requirements.
return context.identified_facet_types().Add(
{key, partially_identified, extends, impls});
{key, partially_identified, extends, impls, rewrites});
}
auto TryToIdentifyFacetType(Context& context, SemIR::LocId loc_id,
+3 -1
View File
@@ -44,7 +44,7 @@ static auto RequiredLess(const IdentifiedFacetType::RequiredImpl& lhs,
IdentifiedFacetType::IdentifiedFacetType(
IdentifiedFacetTypeKey key, bool partially_identified,
llvm::ArrayRef<RequiredImpl> extends,
llvm::ArrayRef<RequiredImpl> self_impls)
llvm::ArrayRef<RequiredImpl> self_impls, llvm::ArrayRef<Rewrite> rewrites)
: key_(key) {
required_impls_.reserve(extends.size() + self_impls.size());
llvm::append_range(required_impls_, extends);
@@ -70,6 +70,8 @@ IdentifiedFacetType::IdentifiedFacetType(
// same constituents but a more complete set of required interfaces.
key_.num_require_impls = required_impls_.size();
}
rewrites_.assign(rewrites);
}
auto AddCanonicalWitnessesBlock(File& sem_ir,
+18 -2
View File
@@ -42,16 +42,26 @@ struct IdentifiedFacetType {
friend auto operator==(const RequiredImpl& lhs, const RequiredImpl& rhs)
-> bool = default;
};
struct Rewrite {
ConstantId lhs;
ConstantId rhs;
friend auto operator==(const Rewrite& lhs, const Rewrite& rhs)
-> bool = default;
};
IdentifiedFacetType(IdentifiedFacetTypeKey key, bool partially_identified,
llvm::ArrayRef<RequiredImpl> extends,
llvm::ArrayRef<RequiredImpl> self_impls);
llvm::ArrayRef<RequiredImpl> self_impls,
llvm::ArrayRef<Rewrite> rewrites);
// The order here defines the order of impl witnesses for this facet type.
auto required_impls() const -> llvm::ArrayRef<RequiredImpl> {
return required_impls_;
}
auto rewrites() const -> llvm::ArrayRef<Rewrite> { return rewrites_; }
// Can this be used to the right of an `as` in an `impl` declaration?
auto is_valid_impl_as_target() const -> bool {
return interface_id_.has_value();
@@ -89,6 +99,12 @@ struct IdentifiedFacetType {
// constraint. Sorted and deduplicated.
llvm::SmallVector<RequiredImpl> required_impls_;
// Rewrite constraints from a facet type and any named constraints that it
// references, flattened to a single list with `.Self` replaced on the RHS.
// The witness on the LHS should be ignored other than to specify in which
// interface the associated constant is being rewritten.
llvm::SmallVector<Rewrite> rewrites_;
// The single interface from `required_impls` to implement if this is
// the facet type to the right of an `impl`...`as`, or `None` if no such
// single interface.
@@ -120,7 +136,7 @@ auto AddCanonicalWitnessesBlock(File& sem_ir,
namespace Carbon {
extern template class CanonicalValueStore<
SemIR::IdentifiedFacetTypeId, SemIR::IdentifiedFacetTypeKey,
Tag<SemIR::CheckIRId>, SemIR ::IdentifiedFacetType>;
Tag<SemIR::CheckIRId>, SemIR::IdentifiedFacetType>;
extern template class ValueStore<SemIR::IdentifiedFacetTypeId,
SemIR::IdentifiedFacetType,
Tag<SemIR::CheckIRId>>;