Procházet zdrojové kódy

Support CARBON_KIND with Any types (#6828)

This uses the `CARBON_KIND_ANY(AnyImportRef, auto import_ref):` syntax
that seemed to be favored [on
Discord](https://discord.com/channels/655572317891461132/655578254970716160/1478486848207720478).

This converted uses in the `sem_ir` directory to show it works
initially, then added `check` for full coverage plus validating the
`SemIR::` namespace discard.

Note in inst_namer.cpp, AnyBindingPattern includes FormBindingPattern
which wasn't previously handled.

I'm disabling clang-format because I think it formats with readability
issues, e.g.:

```
#define CARBON_KIND_ANY_EXPAND_AnyBinding(X, SEP)                        \
  X(::Carbon::SemIR::AliasBinding)                                       \
  SEP X(::Carbon::SemIR::FormBinding) SEP X(::Carbon::SemIR::RefBinding) \
      SEP X(::Carbon::SemIR::SymbolicBinding)                            \
          SEP X(::Carbon::SemIR::ValueBinding)
```

Since `SEP` is typically a comma, it's also a nuisance to treat as an
argument to `X` (which could get better results).

Assisted-by: Google Antigravity with Gemini 3 Flash
Jon Ross-Perkins před 1 měsícem
rodič
revize
002b7c74ea

+ 93 - 26
toolchain/base/kind_switch.h

@@ -20,6 +20,10 @@
 //     case CARBON_KIND(SomeInstType inst): {
 //       return inst.typed_field;
 //     }
+//     case CARBON_KIND_ANY(AnyKind, any_inst): {
+//       return any_inst.typed_field;
+//     }
+//
 //     case OtherType1::Kind:
 //     case OtherType2::Kind:
 //       return value;
@@ -27,17 +31,62 @@
 //       return default_value;
 //   }
 //
-// For compatibility, this requires:
+// When used on kind-like types, this requires:
 //
 // - The type passed to `CARBON_KIND_SWITCH` has `.kind()` to switch on, and
 //   `.As<CaseT>` for `CARBON_KIND` to cast to.
 // - Each type passed to `CARBON_KIND` (`CaseT` above) provides
 //   `CaseT::Kind`, which is passed to the `case` keyword.
 //   `CaseT::Kind::RawEnumType` is the type returned by `.kind()`.
+// - Each type passed to `CARBON_KIND_ANY` must have a macro of the form:
+//   ```
+//   #define AnyKind_CARBON_KIND_ANY_EXPAND   \
+//     CARBON_KIND_ANY_EXPAND_BEGIN CARBON_KIND_ANY_EXPAND_CASE(Kind1) \
+//     CARBON_KIND_ANY_EXPAND_SEP   CARBON_KIND_ANY_EXPAND_CASE(Kind2) \
+//     ...
+//     CARBON_KIND_ANY_EXPAND_SEP   CARBON_KIND_ANY_EXPAND_CASE(KindN)
+//   ```
+//   Note the prefix `,` is required.
+//
+// When used with `std::variant` (e.g., `CARBON_KIND_SWITCH(variant_value)`),
+// members of the variant are passed to `CARBON_KIND`, instead of types that
+// have a `::Kind` member.
+
+// Produces a switch statement on value.kind().
+#define CARBON_KIND_SWITCH(value)                       \
+  switch (                                              \
+      auto&& carbon_internal_kind_switch_value = value; \
+      ::Carbon::Internal::Kind::SwitchOn(carbon_internal_kind_switch_value))
+
+// Produces a case-compatible block of code that also instantiates a local typed
+// variable. typed_variable_decl looks like `int i`; the `CARBON_KIND` value
+// will be cast to the provided type.
 //
-// Note, this is currently used primarily for Inst in toolchain. When more
-// use-cases are added, it would be worth considering whether the API
-// requirements should change.
+// Because of the dangling else, braces should always be used with a `case
+// CARBON_KIND`. Otherwise, only the first statement is going to see the
+// variable. Even if that sometimes works, it can lead to confusing issues when
+// statements are added, and `if`/`else` coding style already requires braces.
+#define CARBON_KIND(typed_variable_decl)               \
+  CARBON_KIND_INTERNAL_CASE_VALUE(typed_variable_decl) \
+      : CARBON_KIND_INTERNAL_DECLARE(typed_variable_decl)
+
+// Macros for clients to add support for `Type_CARBON_KIND_ANY_EXPAND` (see
+// example above).
+#define CARBON_KIND_ANY_EXPAND_CASE(X) CARBON_KIND_INTERNAL_CASE_VALUE(X)
+#define CARBON_KIND_ANY_EXPAND_BEGIN ,
+#define CARBON_KIND_ANY_EXPAND_SEP : case
+
+// Produces a case-compatible block of code that also instantiates a local typed
+// variable. Versus `CARBON_KIND(int i)`, note this requires a comma after the
+// type, as in `CARBON_KIND_ANY(AnyKind, i)`.
+#define CARBON_KIND_ANY(Type, variable_name)               \
+  CARBON_KIND_ANY_INTERNAL_WITH_SUFFIX(Type variable_name, \
+                                       Type##_CARBON_KIND_ANY_EXPAND)
+
+// -----------------------------------------------------------------------------
+// Internal implementation details follow.
+// -----------------------------------------------------------------------------
+
 namespace Carbon::Internal::Kind {
 
 template <typename T>
@@ -243,34 +292,52 @@ auto Cast(SwitchT&& kind_switch_value) -> decltype(auto) {
 #define CARBON_INTERNAL_KIND_LABEL(Line) \
   CARBON_INTERNAL_KIND_MERGE(carbon_internal_kind_case_, Line)
 
-}  // namespace Carbon::Internal::Kind
+// To extract the type from an argument, we wrap it in a lambda and will use
+// `function_traits` to extract the type. This supports `typed_param` either
+// being `Type name`, or just `Type`.
+#define CARBON_KIND_INTERNAL_WRAP_TYPE(typed_param) \
+  decltype([]([[maybe_unused]] typed_param) {})
 
-// Produces a switch statement on value.kind().
-#define CARBON_KIND_SWITCH(value)                       \
-  switch (                                              \
-      auto&& carbon_internal_kind_switch_value = value; \
-      ::Carbon::Internal::Kind::SwitchOn(carbon_internal_kind_switch_value))
+// Produces the value for a `case` statement on the type of `typed_param`.
+#define CARBON_KIND_INTERNAL_CASE_VALUE(typed_param) \
+  ::Carbon::Internal::Kind::ForCase<                 \
+      decltype(carbon_internal_kind_switch_value),   \
+      CARBON_KIND_INTERNAL_WRAP_TYPE(typed_param)>()
 
-// Produces a case-compatible block of code that also instantiates a local typed
-// variable. typed_variable_decl looks like `int i`, with a space.
+// Produces a declaration using `typed_variable_decl`.
 //
 // This uses `if` to scope the variable, and provides a dangling `else` in order
 // to prevent accidental `else` use. The label allows `:` to follow the macro
 // name, making it look more like a typical `case`.
+#define CARBON_KIND_INTERNAL_DECLARE(typed_variable_decl)                \
+  if (typed_variable_decl =                                              \
+          ::Carbon::Internal::Kind::Cast<CARBON_KIND_INTERNAL_WRAP_TYPE( \
+              typed_variable_decl)>(                                     \
+              std::forward<decltype(carbon_internal_kind_switch_value)>( \
+                  carbon_internal_kind_switch_value));                   \
+      false) {                                                           \
+  } else [[maybe_unused]]                                                \
+    CARBON_INTERNAL_KIND_LABEL(__LINE__)
+
+// Helper for `CARBON_KIND_ANY` that expands the now-suffixed macro.
+#define CARBON_KIND_ANY_INTERNAL_WITH_SUFFIX(typed_variable_decl,         \
+                                             Type_CARBON_KIND_ANY_EXPAND) \
+  CARBON_KIND_ANY_INTERNAL_IMPL(typed_variable_decl,                      \
+                                Type_CARBON_KIND_ANY_EXPAND)
+
+// Helper for `CARBON_KIND_ANY` that forms the final case setup. The variadic
+// arguments are the expansion of `Type_CARBON_KIND_ANY_EXPAND`, which may
+// contain commas.
 //
-// Because of the dangling else, braces should always be used with a `case
-// CARBON_KIND`. Otherwise, only the first statement is going to see the
-// variable. Even if that sometimes works, it can lead to confusing issues when
-// statements are added, and `if`/`else` coding style already requires braces.
-#define CARBON_KIND(typed_variable_decl)                                   \
-  ::Carbon::Internal::Kind::ForCase<                                       \
-      decltype(carbon_internal_kind_switch_value),                         \
-      decltype([]([[maybe_unused]] typed_variable_decl) {})>()             \
-      : if (typed_variable_decl = ::Carbon::Internal::Kind::Cast<          \
-                decltype([]([[maybe_unused]] typed_variable_decl) {})>(    \
-                std::forward<decltype(carbon_internal_kind_switch_value)>( \
-                    carbon_internal_kind_switch_value));                   \
-            false) {}                                                      \
-  else [[maybe_unused]] CARBON_INTERNAL_KIND_LABEL(__LINE__)
+// As a consequence of the macro suffixing along with the required prefix comma
+// in `Type_CARBON_KIND_ANY_EXPAND`, input of `Namespace::Type` will have become
+// `Namespace::, Type_CARBON_KIND_ANY_EXPAND`, and `DiscardNamespace` exists to
+// discard `Namespace::`.
+#define CARBON_KIND_ANY_INTERNAL_IMPL(typed_variable_decl, DiscardNamespace, \
+                                      ...)                                   \
+  __VA_ARGS__:                                                               \
+  CARBON_KIND_INTERNAL_DECLARE(typed_variable_decl)
+
+}  // namespace Carbon::Internal::Kind
 
 #endif  // CARBON_TOOLCHAIN_BASE_KIND_SWITCH_H_

+ 2 - 9
toolchain/check/handle_binding_pattern.cpp

@@ -520,18 +520,11 @@ static auto MarkPatternUnused(Context& context, SemIR::InstId inst_id) -> bool {
     auto current_inst_id = worklist.pop_back_val();
     auto inst = context.insts().Get(current_inst_id);
     CARBON_KIND_SWITCH(inst) {
-      case SemIR::OutParamPattern::Kind:
-      case SemIR::RefParamPattern::Kind:
-      case SemIR::ValueParamPattern::Kind:
-      case SemIR::VarParamPattern::Kind: {
-        auto param = inst.As<SemIR::AnyParamPattern>();
+      case CARBON_KIND_ANY(SemIR::AnyParamPattern, param): {
         worklist.push_back(param.subpattern_id);
         break;
       }
-      case SemIR::RefBindingPattern::Kind:
-      case SemIR::SymbolicBindingPattern::Kind:
-      case SemIR::ValueBindingPattern::Kind: {
-        auto bind = inst.As<SemIR::AnyBindingPattern>();
+      case CARBON_KIND_ANY(SemIR::AnyBindingPattern, bind): {
         auto& name = context.entity_names().Get(bind.entity_name_id);
         name.is_unused = true;
         // We treat `_` as not marking the pattern as unused for the purpose of

+ 2 - 9
toolchain/check/handle_function.cpp

@@ -602,18 +602,11 @@ static auto CheckUnusedBindingsInPattern(Context& context,
     auto current_id = work_list.pop_back_val();
     auto inst = context.insts().Get(current_id);
     CARBON_KIND_SWITCH(inst) {
-      case SemIR::OutParamPattern::Kind:
-      case SemIR::RefParamPattern::Kind:
-      case SemIR::ValueParamPattern::Kind:
-      case SemIR::VarParamPattern::Kind: {
-        auto param = inst.As<SemIR::AnyParamPattern>();
+      case CARBON_KIND_ANY(SemIR::AnyParamPattern, param): {
         work_list.push_back(param.subpattern_id);
         break;
       }
-      case SemIR::RefBindingPattern::Kind:
-      case SemIR::SymbolicBindingPattern::Kind:
-      case SemIR::ValueBindingPattern::Kind: {
-        auto bind = inst.As<SemIR::AnyBindingPattern>();
+      case CARBON_KIND_ANY(SemIR::AnyBindingPattern, bind): {
         auto& entity_name = context.entity_names().Get(bind.entity_name_id);
         // We need special treatment for the name "_" which is implicitly
         // unused but actually permitted in declarations.

+ 20 - 30
toolchain/check/merge.cpp

@@ -248,40 +248,30 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
       return false;
     }
 
-    switch (new_param_pattern.kind()) {
-      case SemIR::FormParamPattern::Kind:
-      case SemIR::OutParamPattern::Kind:
-      case SemIR::RefParamPattern::Kind:
-      case SemIR::ValueParamPattern::Kind:
-      case SemIR::VarParamPattern::Kind: {
+    CARBON_KIND_SWITCH(new_param_pattern) {
+      case CARBON_KIND_ANY(SemIR::AnyParamPattern, new_any_param_pattern): {
+        auto prev_any_param_pattern =
+            prev_param_pattern.As<SemIR::AnyParamPattern>();
         pattern_stack.push_back(
-            {.prev_id =
-                 prev_param_pattern.As<SemIR::AnyParamPattern>().subpattern_id,
-             .new_id =
-                 new_param_pattern.As<SemIR::AnyParamPattern>().subpattern_id});
+            {.prev_id = prev_any_param_pattern.subpattern_id,
+             .new_id = new_any_param_pattern.subpattern_id});
         break;
       }
-      case SemIR::VarPattern::Kind:
-        pattern_stack.push_back(
-            {.prev_id =
-                 prev_param_pattern.As<SemIR::VarPattern>().subpattern_id,
-             .new_id =
-                 new_param_pattern.As<SemIR::VarPattern>().subpattern_id});
+      case CARBON_KIND(SemIR::VarPattern new_var_pattern): {
+        auto prev_var_pattern = prev_param_pattern.As<SemIR::VarPattern>();
+        pattern_stack.push_back({.prev_id = prev_var_pattern.subpattern_id,
+                                 .new_id = new_var_pattern.subpattern_id});
         break;
-      case SemIR::FormBindingPattern::Kind:
-      case SemIR::RefBindingPattern::Kind:
-      case SemIR::SymbolicBindingPattern::Kind:
-      case SemIR::ValueBindingPattern::Kind: {
-        auto new_name_id =
-            context.entity_names()
-                .Get(new_param_pattern.As<SemIR::AnyBindingPattern>()
-                         .entity_name_id)
-                .name_id;
-        auto prev_name_id =
-            context.entity_names()
-                .Get(prev_param_pattern.As<SemIR::AnyBindingPattern>()
-                         .entity_name_id)
-                .name_id;
+      }
+      case CARBON_KIND_ANY(SemIR::AnyBindingPattern, new_any_binding_pattern): {
+        auto prev_any_binding_pattern =
+            prev_param_pattern.As<SemIR::AnyBindingPattern>();
+        auto new_name_id = context.entity_names()
+                               .Get(new_any_binding_pattern.entity_name_id)
+                               .name_id;
+        auto prev_name_id = context.entity_names()
+                                .Get(prev_any_binding_pattern.entity_name_id)
+                                .name_id;
 
         if (!check_self && new_name_id == SemIR::NameId::SelfValue &&
             prev_name_id == SemIR::NameId::SelfValue) {

+ 6 - 12
toolchain/check/pattern_match.cpp

@@ -634,20 +634,14 @@ auto MatchContext::EmitPatternMatch(Context& context,
       });
   auto pattern = context.insts().Get(entry.pattern_id);
   CARBON_KIND_SWITCH(pattern) {
-    case SemIR::RefBindingPattern::Kind:
-    case SemIR::SymbolicBindingPattern::Kind:
-    case SemIR::ValueBindingPattern::Kind:
-    case SemIR::FormBindingPattern::Kind:
-      DoEmitPatternMatch(context, pattern.As<SemIR::AnyBindingPattern>(),
-                         entry);
+    case CARBON_KIND_ANY(SemIR::AnyBindingPattern, any_binding_pattern): {
+      DoEmitPatternMatch(context, any_binding_pattern, entry);
       break;
-    case SemIR::FormParamPattern::Kind:
-    case SemIR::RefParamPattern::Kind:
-    case SemIR::ValueParamPattern::Kind:
-    case SemIR::VarParamPattern::Kind:
-    case SemIR::OutParamPattern::Kind:
-      DoEmitPatternMatch(context, pattern.As<SemIR::AnyParamPattern>(), entry);
+    }
+    case CARBON_KIND_ANY(SemIR::AnyParamPattern, any_param_pattern): {
+      DoEmitPatternMatch(context, any_param_pattern, entry);
       break;
+    }
     case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): {
       DoEmitPatternMatch(context, return_slot_pattern, entry);
       break;

+ 12 - 12
toolchain/check/testdata/function/call/form.carbon

@@ -226,15 +226,15 @@ fn F() ->? ref i32;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %.loc4_6.1: %pattern_type.7ce = form_binding_pattern x [concrete]
-// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %.loc4_6.1 [concrete]
+// CHECK:STDOUT:     %x.patt: %pattern_type.7ce = form_binding_pattern x [concrete]
+// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %x.patt [concrete]
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %x.param: ref %i32 = ref_param call_param0
 // CHECK:STDOUT:     %.loc4_15.1: Core.Form = splice_block %.loc4_15.2 [concrete = constants.%.1da] {
 // CHECK:STDOUT:       %i32: type = type_literal constants.%i32 [concrete = constants.%i32]
 // CHECK:STDOUT:       %.loc4_15.2: Core.Form = ref_form %i32 [concrete = constants.%.1da]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %.loc4_6.2: ref %i32 = form_binding x, %x.param
+// CHECK:STDOUT:     %x: ref %i32 = form_binding x, %x.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -286,15 +286,15 @@ fn F() ->? ref i32;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %.loc4_6.1: %pattern_type.7ce = form_binding_pattern x [concrete]
-// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %.loc4_6.1 [concrete]
+// CHECK:STDOUT:     %x.patt: %pattern_type.7ce = form_binding_pattern x [concrete]
+// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %x.patt [concrete]
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %x.param: ref %i32 = ref_param call_param0
 // CHECK:STDOUT:     %.loc4_15.1: Core.Form = splice_block %.loc4_15.2 [concrete = constants.%.324] {
 // CHECK:STDOUT:       %i32: type = type_literal constants.%i32 [concrete = constants.%i32]
 // CHECK:STDOUT:       %.loc4_15.2: Core.Form = init_form %i32, call_param<none> [concrete = constants.%.324]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %.loc4_6.2: ref %i32 = form_binding x, %x.param
+// CHECK:STDOUT:     %x: ref %i32 = form_binding x, %x.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -354,15 +354,15 @@ fn F() ->? ref i32;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %.loc4_6.1: %pattern_type.7ce = form_binding_pattern x [concrete]
-// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %.loc4_6.1 [concrete]
+// CHECK:STDOUT:     %x.patt: %pattern_type.7ce = form_binding_pattern x [concrete]
+// CHECK:STDOUT:     %.loc4_7: %pattern_type.7ce = form_param_pattern %x.patt [concrete]
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %x.param: %i32 = value_param call_param0
 // CHECK:STDOUT:     %.loc4_15.1: Core.Form = splice_block %.loc4_15.2 [concrete = constants.%.754] {
 // CHECK:STDOUT:       %i32: type = type_literal constants.%i32 [concrete = constants.%i32]
 // CHECK:STDOUT:       %.loc4_15.2: Core.Form = value_form %i32 [concrete = constants.%.754]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %.loc4_6.2: %i32 = form_binding x, %x.param
+// CHECK:STDOUT:     %x: %i32 = form_binding x, %x.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -396,8 +396,8 @@ fn F() ->? ref i32;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %.loc4_6.1: %pattern_type = form_binding_pattern x [concrete]
-// CHECK:STDOUT:     %.loc4_7: %pattern_type = form_param_pattern %.loc4_6.1 [concrete]
+// CHECK:STDOUT:     %x.patt: %pattern_type = form_binding_pattern x [concrete]
+// CHECK:STDOUT:     %.loc4_7: %pattern_type = form_param_pattern %x.patt [concrete]
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %x.param: ref %empty_tuple.type = ref_param call_param0
 // CHECK:STDOUT:     %.loc4_15.1: Core.Form = splice_block %.loc4_15.2 [concrete = constants.%.9f9] {
@@ -405,7 +405,7 @@ fn F() ->? ref i32;
 // CHECK:STDOUT:       %.loc4_20.2: type = converted %.loc4_20.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
 // CHECK:STDOUT:       %.loc4_15.2: Core.Form = ref_form %.loc4_20.2 [concrete = constants.%.9f9]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %.loc4_6.2: ref %empty_tuple.type = form_binding x, %x.param
+// CHECK:STDOUT:     %x: ref %empty_tuple.type = form_binding x, %x.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 8 - 13
toolchain/sem_ir/formatter.cpp

@@ -24,6 +24,7 @@
 #include "toolchain/sem_ir/formatter_chunks.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst_categories.h"
 #include "toolchain/sem_ir/name_scope.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/vtable.h"
@@ -1042,23 +1043,17 @@ auto Formatter::FormatInstArgAndKind(Inst::ArgAndKind arg_and_kind) -> void {
 
 auto Formatter::FormatInstRhs(Inst inst) -> void {
   CARBON_KIND_SWITCH(inst) {
-    case InstKind::ArrayInit:
-    case InstKind::StructInit:
-    case InstKind::TupleInit: {
-      auto init = inst.As<AnyAggregateInit>();
+    case CARBON_KIND_ANY(AnyAggregateInit, init): {
       FormatArgs(init.elements_id);
       return;
     }
 
-    case InstKind::ImportRefLoaded:
-    case InstKind::ImportRefUnloaded:
-      FormatImportRefRhs(inst.As<AnyImportRef>());
+    case CARBON_KIND_ANY(AnyImportRef, import_ref): {
+      FormatImportRefRhs(import_ref);
       return;
+    }
 
-    case InstKind::OutParam:
-    case InstKind::RefParam:
-    case InstKind::ValueParam: {
-      auto param = inst.As<AnyParam>();
+    case CARBON_KIND_ANY(AnyParam, param): {
       FormatArgs(param.index);
       // Omit pretty_name because it's an implementation detail of
       // pretty-printing.
@@ -1140,7 +1135,7 @@ auto Formatter::FormatInstRhs(Inst inst) -> void {
       return;
     }
 
-    case InstKind::ImportCppDecl: {
+    case ImportCppDecl::Kind: {
       FormatImportCppDeclRhs();
       return;
     }
@@ -1229,7 +1224,7 @@ auto Formatter::FormatInstRhs(Inst inst) -> void {
       return;
     }
 
-    case InstKind::ReturnSlotPattern:
+    case ReturnSlotPattern::Kind:
       // No-op because type_id is the only semantically significant field,
       // and it's handled separately.
       return;

+ 184 - 21
toolchain/sem_ir/inst_categories.h

@@ -21,7 +21,7 @@ namespace Carbon::SemIR {
 // of a category inst type:
 //
 // struct MyCategory {
-//   using CategoryInfo = CategoryOf<X, Y, Z>;
+//   using CategoryInfo = CARBON_INST_CATEGORY_INFO(MyCategory);
 //   InstKind kind;
 //   ...
 // }
@@ -31,11 +31,31 @@ struct CategoryOf {
   static constexpr InstKind Kinds[] = {TypedInsts::Kind...};
 };
 
+// For each category, we provide `CategoryName_CARBON_INST_CATEGORY` for the
+// `CARBON_KIND_ANY` macro. This macro uses the same expansion to provide a
+// `CategoryOf` for the category.
+#define CARBON_INST_CATEGORY_INFO(Name)        \
+  CategoryOf<Name##_CARBON_INST_CATEGORY(      \
+      CARBON_INST_CATEGORY_INFO_INTERNAL_NAME, \
+      CARBON_INST_CATEGORY_INFO_INTERNAL_COMMA)>
+#define CARBON_INST_CATEGORY_INFO_INTERNAL_NAME(Name) Name
+#define CARBON_INST_CATEGORY_INFO_INTERNAL_COMMA ,
+
+// clang-format off
+#define AnyAggregateAccess_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::ClassElementAccess) Sep            \
+  X(::Carbon::SemIR::StructAccess) Sep                  \
+  X(::Carbon::SemIR::TupleAccess)
+// clang-format on
+
+#define AnyAggregateAccess_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyAggregateAccess_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for aggregate access nodes, which access a fixed
 // element of an aggregate.
 struct AnyAggregateAccess {
-  using CategoryInfo =
-      CategoryOf<ClassElementAccess, StructAccess, TupleAccess>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyAggregateAccess);
 
   InstKind kind;
   TypeId type_id;
@@ -43,9 +63,21 @@ struct AnyAggregateAccess {
   ElementIndex index;
 };
 
+// clang-format off
+#define AnyAggregateInit_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::ArrayInit) Sep                   \
+  X(::Carbon::SemIR::ClassInit) Sep                   \
+  X(::Carbon::SemIR::StructInit) Sep                  \
+  X(::Carbon::SemIR::TupleInit)
+// clang-format on
+
+#define AnyAggregateInit_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyAggregateInit_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for all kinds of aggregate initialization.
 struct AnyAggregateInit {
-  using CategoryInfo = CategoryOf<ArrayInit, ClassInit, StructInit, TupleInit>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyAggregateInit);
 
   InstKind kind;
   TypeId type_id;
@@ -53,20 +85,41 @@ struct AnyAggregateInit {
   DestInstId dest_id;
 };
 
+// clang-format off
+#define AnyAggregateValue_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::StructValue) Sep                  \
+  X(::Carbon::SemIR::TupleValue)
+// clang-format on
+
+#define AnyAggregateValue_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyAggregateValue_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for all kinds of aggregate value.
 struct AnyAggregateValue {
-  using CategoryInfo = CategoryOf<StructValue, TupleValue>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyAggregateValue);
 
   InstKind kind;
   TypeId type_id;
   InstBlockId elements_id;
 };
 
+// clang-format off
+#define AnyBindingPattern_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::FormBindingPattern) Sep           \
+  X(::Carbon::SemIR::RefBindingPattern) Sep            \
+  X(::Carbon::SemIR::SymbolicBindingPattern) Sep       \
+  X(::Carbon::SemIR::ValueBindingPattern)
+// clang-format on
+
+#define AnyBindingPattern_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyBindingPattern_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for various `*binding_pattern` nodes.
 struct AnyBindingPattern {
   // TODO: Also handle TemplateBindingPattern once it exists.
-  using CategoryInfo = CategoryOf<FormBindingPattern, RefBindingPattern,
-                                  SymbolicBindingPattern, ValueBindingPattern>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyBindingPattern);
 
   InstKind kind;
 
@@ -80,11 +133,23 @@ struct AnyBindingPattern {
   EntityNameId entity_name_id;
 };
 
+// clang-format off
+#define AnyBinding_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::AliasBinding) Sep          \
+  X(::Carbon::SemIR::FormBinding) Sep           \
+  X(::Carbon::SemIR::RefBinding) Sep            \
+  X(::Carbon::SemIR::SymbolicBinding) Sep       \
+  X(::Carbon::SemIR::ValueBinding)
+// clang-format on
+
+#define AnyBinding_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyBinding_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for various `bind*` nodes.
 struct AnyBinding {
   // TODO: Also handle BindTemplateName once it exists.
-  using CategoryInfo = CategoryOf<AliasBinding, FormBinding, RefBinding,
-                                  SymbolicBinding, ValueBinding>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyBinding);
 
   InstKind kind;
   TypeId type_id;
@@ -95,11 +160,24 @@ struct AnyBinding {
   InstId value_id;
 };
 
+// clang-format off
+#define AnyBindingOrExportDecl_CARBON_INST_CATEGORY(X, Sep)            \
+  X(::Carbon::SemIR::AliasBinding) Sep                                 \
+  X(::Carbon::SemIR::FormBinding) Sep                                  \
+  X(::Carbon::SemIR::RefBinding) Sep                                   \
+  X(::Carbon::SemIR::SymbolicBinding) Sep                              \
+  X(::Carbon::SemIR::ValueBinding) Sep                                 \
+  X(::Carbon::SemIR::ExportDecl)
+// clang-format on
+
+#define AnyBindingOrExportDecl_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyBindingOrExportDecl_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for various `bind*` nodes, and `export name`.
 struct AnyBindingOrExportDecl {
   // TODO: Also handle BindTemplateName once it exists.
-  using CategoryInfo = CategoryOf<AliasBinding, FormBinding, RefBinding,
-                                  SymbolicBinding, ValueBinding, ExportDecl>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyBindingOrExportDecl);
 
   InstKind kind;
   TypeId type_id;
@@ -107,9 +185,20 @@ struct AnyBindingOrExportDecl {
   InstId value_id;
 };
 
+// clang-format off
+#define AnyBranch_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::Branch) Sep               \
+  X(::Carbon::SemIR::BranchIf) Sep             \
+  X(::Carbon::SemIR::BranchWithArg)
+// clang-format on
+
+#define AnyBranch_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyBranch_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for all kinds of `Branch*` node.
 struct AnyBranch {
-  using CategoryInfo = CategoryOf<Branch, BranchIf, BranchWithArg>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyBranch);
 
   InstKind kind;
   // Branches don't produce a value, so have no type.
@@ -118,10 +207,20 @@ struct AnyBranch {
   AnyRawId arg1;
 };
 
+// clang-format off
+#define AnyFoundationDecl_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::AdaptDecl) Sep                    \
+  X(::Carbon::SemIR::BaseDecl)
+// clang-format on
+
+#define AnyFoundationDecl_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyFoundationDecl_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for declarations describing the foundation type of a
 // class -- either its adapted type or its base class.
 struct AnyFoundationDecl {
-  using CategoryInfo = CategoryOf<AdaptDecl, BaseDecl>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyFoundationDecl);
 
   InstKind kind;
   TypeId type_id;
@@ -130,9 +229,19 @@ struct AnyFoundationDecl {
   AnyRawId arg1;
 };
 
+// clang-format off
+#define AnyImportRef_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::ImportRefLoaded) Sep \
+  X(::Carbon::SemIR::ImportRefUnloaded)
+// clang-format on
+
+#define AnyImportRef_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyImportRef_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // Common representation for all kinds of `ImportRef*` node.
 struct AnyImportRef {
-  using CategoryInfo = CategoryOf<ImportRefUnloaded, ImportRefLoaded>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyImportRef);
 
   InstKind kind;
   TypeId type_id;
@@ -142,9 +251,20 @@ struct AnyImportRef {
   EntityNameId entity_name_id;
 };
 
+// clang-format off
+#define AnyParam_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::OutParam) Sep            \
+  X(::Carbon::SemIR::RefParam) Sep            \
+  X(::Carbon::SemIR::ValueParam)
+// clang-format on
+
+#define AnyParam_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyParam_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // A `Call` parameter for a function or other parameterized block.
 struct AnyParam {
-  using CategoryInfo = CategoryOf<OutParam, RefParam, ValueParam>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyParam);
 
   InstKind kind;
   TypeId type_id;
@@ -156,12 +276,23 @@ struct AnyParam {
   NameId pretty_name_id;
 };
 
+// clang-format off
+#define AnyParamPattern_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::FormParamPattern) Sep           \
+  X(::Carbon::SemIR::OutParamPattern) Sep            \
+  X(::Carbon::SemIR::RefParamPattern) Sep            \
+  X(::Carbon::SemIR::ValueParamPattern) Sep          \
+  X(::Carbon::SemIR::VarParamPattern)
+// clang-format on
+
+#define AnyParamPattern_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyParamPattern_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // A pattern that represents a `Call` parameter. It delegates to subpattern_id
 // in pattern matching.
 struct AnyParamPattern {
-  using CategoryInfo =
-      CategoryOf<FormParamPattern, OutParamPattern, RefParamPattern,
-                 ValueParamPattern, VarParamPattern>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyParamPattern);
 
   InstKind kind;
 
@@ -171,9 +302,20 @@ struct AnyParamPattern {
   InstId subpattern_id;
 };
 
+// clang-format off
+#define AnyPrimitiveForm_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::InitForm) Sep                    \
+  X(::Carbon::SemIR::RefForm) Sep                     \
+  X(::Carbon::SemIR::ValueForm)
+// clang-format on
+
+#define AnyPrimitiveForm_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyPrimitiveForm_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // An inst that represents a primitive form.
 struct AnyPrimitiveForm {
-  using CategoryInfo = CategoryOf<InitForm, RefForm, ValueForm>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyPrimitiveForm);
 
   InstKind kind;
 
@@ -186,11 +328,22 @@ struct AnyPrimitiveForm {
   AnyRawId arg1;
 };
 
+// clang-format off
+#define AnyQualifiedType_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::ConstType) Sep                   \
+  X(::Carbon::SemIR::MaybeUnformedType) Sep           \
+  X(::Carbon::SemIR::PartialType)
+// clang-format on
+
+#define AnyQualifiedType_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyQualifiedType_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // A type qualifier that wraps another type and has the same object
 // representation. Qualifiers are arranged so that adding a qualifier is
 // generally safe, and removing a qualifier is not necessarily safe or correct.
 struct AnyQualifiedType {
-  using CategoryInfo = CategoryOf<ConstType, PartialType, MaybeUnformedType>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyQualifiedType);
 
   InstKind kind;
 
@@ -198,9 +351,19 @@ struct AnyQualifiedType {
   TypeInstId inner_id;
 };
 
+// clang-format off
+#define AnyStructType_CARBON_INST_CATEGORY(X, Sep) \
+  X(::Carbon::SemIR::CustomLayoutType) Sep         \
+  X(::Carbon::SemIR::StructType)
+// clang-format on
+
+#define AnyStructType_CARBON_KIND_ANY_EXPAND                       \
+  CARBON_KIND_ANY_EXPAND_BEGIN AnyStructType_CARBON_INST_CATEGORY( \
+      CARBON_KIND_ANY_EXPAND_CASE, CARBON_KIND_ANY_EXPAND_SEP)
+
 // A struct-like type with a list of named fields.
 struct AnyStructType {
-  using CategoryInfo = CategoryOf<StructType, CustomLayoutType>;
+  using CategoryInfo = CARBON_INST_CATEGORY_INFO(AnyStructType);
 
   InstKind kind;
 

+ 6 - 21
toolchain/sem_ir/inst_namer.cpp

@@ -914,19 +914,11 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddEntityNameAndMaybePush(inst.interface_id, ".assoc_type");
       return;
     }
-    case AliasBinding::Kind:
-    case RefBinding::Kind:
-    case SymbolicBinding::Kind:
-    case ValueBinding::Kind:
-    case ExportDecl::Kind: {
-      auto inst = inst_.As<AnyBindingOrExportDecl>();
+    case CARBON_KIND_ANY(AnyBindingOrExportDecl, inst): {
       AddInstNameId(sem_ir().entity_names().Get(inst.entity_name_id).name_id);
       return;
     }
-    case RefBindingPattern::Kind:
-    case SymbolicBindingPattern::Kind:
-    case ValueBindingPattern::Kind: {
-      auto inst = inst_.As<AnyBindingPattern>();
+    case CARBON_KIND_ANY(AnyBindingPattern, inst): {
       auto name_id = NameId::Underscore;
       if (inst.entity_name_id.has_value()) {
         name_id = sem_ir().entity_names().Get(inst.entity_name_id).name_id;
@@ -951,10 +943,7 @@ auto InstNamer::NamingContext::NameInst() -> void {
       }
       return;
     }
-    case Branch::Kind:
-    case BranchIf::Kind:
-    case BranchWithArg::Kind: {
-      auto branch = inst_.As<AnyBranch>();
+    case CARBON_KIND_ANY(AnyBranch, branch): {
       inst_namer_->AddBlockLabel(scope_id_, LocId(inst_id_), branch);
       return;
     }
@@ -1209,12 +1198,10 @@ auto InstNamer::NamingContext::NameInst() -> void {
       }
       return;
     }
-    case ImportRefUnloaded::Kind:
-    case ImportRefLoaded::Kind: {
+    case CARBON_KIND_ANY(AnyImportRef, inst): {
       // Build the base import name: <package>.<entity-name>
       RawStringOstream out;
 
-      auto inst = inst_.As<AnyImportRef>();
       auto import_ir_inst =
           sem_ir().import_ir_insts().Get(inst.import_ir_inst_id);
       const auto& import_ir =
@@ -1304,10 +1291,8 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddInstNameId(sem_ir().name_scopes().Get(inst.name_scope_id).name_id());
       return;
     }
-    case OutParam::Kind:
-    case RefParam::Kind:
-    case ValueParam::Kind: {
-      AddInstNameId(inst_.As<AnyParam>().pretty_name_id, ".param");
+    case CARBON_KIND_ANY(AnyParam, inst): {
+      AddInstNameId(inst.pretty_name_id, ".param");
       return;
     }
     case OutParamPattern::Kind: