Restructure ArgAndKind as a type-safe generic ID. (#7172)

The key changes here are:
- Relocating and renaming it to align with `IdKind` (and relocating
`ToRaw` and `FromRaw` to follow it).
- Adding a `Dispatch` method that provides a generic overload-based API
for expressing per-ID-kind dispatch, and rewriting existing code to use
it.

Note in particular that using overloads instead of switch cases makes it
possible to generically handle all specializations of a templated ID
type, e.g. `SomeIdType<T>` for all `T`. We have no such templated ID
types yet, but I'm introducing one in a follow-up PR that needs this
capability.
This commit is contained in:
Geoff Romer
2026-05-07 00:13:52 +00:00
committed by GitHub
parent db24042fe5
commit daebbf32fa
10 changed files with 236 additions and 218 deletions
+76 -96
View File
@@ -877,43 +877,25 @@ static constexpr bool HasGetConstantValueOverload = requires {
Accept<auto (*)(EvalContext&, IdT, Phase*)->IdT>(GetConstantValue);
};
using ArgHandlerFnT = auto(EvalContext& context, int32_t arg, Phase* phase)
-> int32_t;
// Returns the arg handler for an `IdKind`.
template <typename... Types>
static auto GetArgHandlerFn(TypeEnum<Types...> id_kind) -> ArgHandlerFnT* {
static constexpr std::array<ArgHandlerFnT*, SemIR::IdKind::NumValues> Table =
{
[](EvalContext& eval_context, int32_t arg, Phase* phase) -> int32_t {
auto id = SemIR::Inst::FromRaw<Types>(arg);
if constexpr (HasGetConstantValueOverload<Types>) {
// If we have a custom `GetConstantValue` overload, call it.
return SemIR::Inst::ToRaw(
GetConstantValue(eval_context, id, phase));
} else {
// Otherwise, we assume the value is already constant.
return arg;
}
}...,
// Invalid and None handling (ordering-sensitive).
[](auto...) -> int32_t { CARBON_FATAL("Unexpected invalid IdKind"); },
[](EvalContext& /*context*/, int32_t arg,
Phase* /*phase*/) -> int32_t { return arg; },
};
return Table[id_kind.ToIndex()];
}
// Given the stored value `arg` of an instruction field and its corresponding
// kind `kind`, returns the constant value to use for that field, if it has a
// constant phase. `*phase` is updated to include the new constant value. If
// the resulting phase is not constant, the returned value is not useful and
// will typically be `NoneIndex`.
static auto GetConstantValueForArg(EvalContext& eval_context,
SemIR::Inst::ArgAndKind arg_and_kind,
Phase* phase) -> int32_t {
return GetArgHandlerFn(arg_and_kind.kind())(eval_context,
arg_and_kind.value(), phase);
SemIR::IdAndKind arg_and_kind, Phase* phase)
-> int32_t {
return arg_and_kind.Dispatch<int32_t>([&]<typename IdT>(IdT id) -> int32_t {
if constexpr (HasGetConstantValueOverload<IdT>) {
// If we have a custom `GetConstantValue` overload, call it.
return SemIR::ToRaw(GetConstantValue(eval_context, id, phase));
} else if constexpr (std::same_as<IdT, SemIR::IdAndKind::InvalidType>) {
CARBON_FATAL("Unexpected invalid IdKind");
} else {
// Otherwise, we assume the value is already constant.
return SemIR::ToRaw(id);
}
});
}
// Given an instruction, replaces its operands with their constant values from
@@ -985,76 +967,74 @@ static auto ResolveSpecificDeclForSpecificId(EvalContext& eval_context,
specific_id);
}
static auto ResolveSpecificDeclForArg(EvalContext& eval_context,
SemIR::FacetTypeId facet_type_id)
-> void {
const auto& info = eval_context.context().facet_types().Get(facet_type_id);
for (const auto& interface : info.extend_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& interface : info.self_impls_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& constraint : info.extend_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, constraint.specific_id);
}
for (const auto& constraint : info.self_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, constraint.specific_id);
}
for (const auto& type_impls : info.type_impls_interfaces) {
ResolveSpecificDeclForSpecificId(eval_context,
type_impls.specific_interface.specific_id);
}
for (const auto& type_impls : info.type_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_named_constraint.specific_id);
}
}
static auto ResolveSpecificDeclForArg(EvalContext& eval_context,
SemIR::SpecificId specific_id) -> void {
ResolveSpecificDeclForSpecificId(eval_context, specific_id);
}
static auto ResolveSpecificDeclForArg(
EvalContext& eval_context, SemIR::SpecificInterfaceId specific_interface_id)
-> void {
ResolveSpecificDeclForSpecificId(eval_context,
eval_context.specific_interfaces()
.Get(specific_interface_id)
.specific_id);
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT> &&
SameAsOneOf<IdT, SemIR::IdAndKind::NoneType, SemIR::DestInstId,
SemIR::EntityNameId, SemIR::InstBlockId, SemIR::InstId,
SemIR::MetaInstId, SemIR::StructTypeFieldsId,
SemIR::TypeInstId>
static auto ResolveSpecificDeclForArg(EvalContext& /*eval_context*/, IdT /*id*/)
-> void {
// These id types have a GetConstantValue() overload but that overload
// does not canonicalize any SpecificId in the value type.
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto ResolveSpecificDeclForArg(EvalContext& /*eval_context*/, IdT /*id*/)
-> void {
if constexpr (HasGetConstantValueOverload<IdT>) {
CARBON_FATAL("Missing case for {0} which has a GetConstantValue() overload",
IdT::Label);
}
}
// Resolves the specific declarations for a specific id in any field of the
// `inst` instruction.
static auto ResolveSpecificDeclForInst(EvalContext& eval_context,
const SemIR::Inst& inst) -> void {
for (auto arg_and_kind : {inst.arg0_and_kind(), inst.arg1_and_kind()}) {
// This switch must handle any field type that has a GetConstantValue()
// overload which canonicalizes a specific (and thus potentially forms a new
// specific) as part of forming its constant value.
CARBON_KIND_SWITCH(arg_and_kind) {
case CARBON_KIND(SemIR::FacetTypeId facet_type_id): {
const auto& info =
eval_context.context().facet_types().Get(facet_type_id);
for (const auto& interface : info.extend_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& interface : info.self_impls_constraints) {
ResolveSpecificDeclForSpecificId(eval_context, interface.specific_id);
}
for (const auto& constraint : info.extend_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context,
constraint.specific_id);
}
for (const auto& constraint : info.self_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(eval_context,
constraint.specific_id);
}
for (const auto& type_impls : info.type_impls_interfaces) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_interface.specific_id);
}
for (const auto& type_impls : info.type_impls_named_constraints) {
ResolveSpecificDeclForSpecificId(
eval_context, type_impls.specific_named_constraint.specific_id);
}
break;
}
case CARBON_KIND(SemIR::SpecificId specific_id): {
ResolveSpecificDeclForSpecificId(eval_context, specific_id);
break;
}
case CARBON_KIND(SemIR::SpecificInterfaceId specific_interface_id): {
ResolveSpecificDeclForSpecificId(eval_context,
eval_context.specific_interfaces()
.Get(specific_interface_id)
.specific_id);
break;
}
// These id types have a GetConstantValue() overload but that overload
// does not canonicalize any SpecificId in the value type.
case SemIR::IdKind::For<SemIR::DestInstId>:
case SemIR::IdKind::For<SemIR::EntityNameId>:
case SemIR::IdKind::For<SemIR::InstBlockId>:
case SemIR::IdKind::For<SemIR::InstId>:
case SemIR::IdKind::For<SemIR::MetaInstId>:
case SemIR::IdKind::For<SemIR::StructTypeFieldsId>:
case SemIR::IdKind::For<SemIR::TypeInstId>:
break;
case SemIR::IdKind::None:
// No arg.
break;
default:
CARBON_CHECK(
!KindHasGetConstantValueOverload(arg_and_kind.kind()),
"Missing case for {0} which has a GetConstantValue() overload",
arg_and_kind.kind());
break;
}
arg_and_kind.Dispatch<void>(
[&](auto id) { ResolveSpecificDeclForArg(eval_context, id); });
}
}