Move type card printing to standalone function for reuse.

PiperOrigin-RevId: 579966858
diff --git a/src/google/protobuf/compiler/cpp/parse_function_generator.cc b/src/google/protobuf/compiler/cpp/parse_function_generator.cc
index a8074df..25bfbbe 100644
--- a/src/google/protobuf/compiler/cpp/parse_function_generator.cc
+++ b/src/google/protobuf/compiler/cpp/parse_function_generator.cc
@@ -621,141 +621,6 @@
   }
 }
 
-static void FormatFieldKind(Formatter& format,
-                            const TailCallTableInfo::FieldEntryInfo& entry) {
-  // In here we convert the runtime value of entry.type_card back into a
-  // sequence of literal enum labels. We use the mnenonic labels for nicer
-  // codegen.
-  namespace fl = internal::field_layout;
-  const uint16_t type_card = entry.type_card;
-  const int rep_index = (type_card & fl::kRepMask) >> fl::kRepShift;
-  const int tv_index = (type_card & fl::kTvMask) >> fl::kTvShift;
-
-  // Use `0|` prefix to eagerly convert the enums to int to avoid enum-enum
-  // operations. They are deprecated in C++20.
-  format("(0 | ");
-  static constexpr const char* kFieldCardNames[] = {"Singular", "Optional",
-                                                    "Repeated", "Oneof"};
-  static_assert((fl::kFcSingular >> fl::kFcShift) == 0, "");
-  static_assert((fl::kFcOptional >> fl::kFcShift) == 1, "");
-  static_assert((fl::kFcRepeated >> fl::kFcShift) == 2, "");
-  static_assert((fl::kFcOneof >> fl::kFcShift) == 3, "");
-
-  format("::_fl::kFc$1$",
-         kFieldCardNames[(type_card & fl::kFcMask) >> fl::kFcShift]);
-
-#define PROTOBUF_INTERNAL_TYPE_CARD_CASE(x) \
-  case fl::k##x:                            \
-    format(" | ::_fl::k" #x);               \
-    break
-
-  switch (type_card & fl::kFkMask) {
-    case fl::kFkString: {
-      switch (type_card & ~fl::kFcMask & ~fl::kRepMask & ~fl::kSplitMask) {
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Bytes);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(RawString);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Utf8String);
-        default:
-          ABSL_LOG(FATAL) << "Unknown type_card: 0x" << type_card;
-      }
-
-      static constexpr const char* kRepNames[] = {"AString", "IString", "Cord",
-                                                  "SPiece", "SString"};
-      static_assert((fl::kRepAString >> fl::kRepShift) == 0, "");
-      static_assert((fl::kRepIString >> fl::kRepShift) == 1, "");
-      static_assert((fl::kRepCord >> fl::kRepShift) == 2, "");
-      static_assert((fl::kRepSPiece >> fl::kRepShift) == 3, "");
-      static_assert((fl::kRepSString >> fl::kRepShift) == 4, "");
-
-      format(" | ::_fl::kRep$1$", kRepNames[rep_index]);
-      break;
-    }
-
-    case fl::kFkMessage: {
-      format(" | ::_fl::kMessage");
-
-      static constexpr const char* kRepNames[] = {nullptr, "Group", "Lazy"};
-      static_assert((fl::kRepGroup >> fl::kRepShift) == 1, "");
-      static_assert((fl::kRepLazy >> fl::kRepShift) == 2, "");
-
-      if (auto* rep = kRepNames[rep_index]) {
-        format(" | ::_fl::kRep$1$", rep);
-      }
-
-      static constexpr const char* kXFormNames[2][4] = {
-          {nullptr, "Default", "Table", "WeakPtr"}, {nullptr, "Eager", "Lazy"}};
-
-      static_assert((fl::kTvDefault >> fl::kTvShift) == 1, "");
-      static_assert((fl::kTvTable >> fl::kTvShift) == 2, "");
-      static_assert((fl::kTvWeakPtr >> fl::kTvShift) == 3, "");
-      static_assert((fl::kTvEager >> fl::kTvShift) == 1, "");
-      static_assert((fl::kTvLazy >> fl::kTvShift) == 2, "");
-
-      if (auto* xform = kXFormNames[rep_index == 2][tv_index]) {
-        format(" | ::_fl::kTv$1$", xform);
-      }
-      break;
-    }
-
-    case fl::kFkMap:
-      format(" | ::_fl::kMap");
-      break;
-
-    case fl::kFkNone:
-      break;
-
-    case fl::kFkVarint:
-    case fl::kFkPackedVarint:
-    case fl::kFkFixed:
-    case fl::kFkPackedFixed: {
-      switch (type_card & ~fl::kFcMask & ~fl::kSplitMask) {
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Bool);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Fixed32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(UInt32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SFixed32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Int32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SInt32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Float);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Enum);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(EnumRange);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(OpenEnum);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Fixed64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(UInt64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SFixed64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Int64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SInt64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Double);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedBool);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFixed32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedUInt32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSFixed32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedInt32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSInt32);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFloat);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedEnum);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedEnumRange);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedOpenEnum);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFixed64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedUInt64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSFixed64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedInt64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSInt64);
-        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedDouble);
-        default:
-          ABSL_LOG(FATAL) << "Unknown type_card: 0x" << type_card;
-      }
-    }
-  }
-
-  if (type_card & fl::kSplitMask) {
-    format(" | ::_fl::kSplitTrue");
-  }
-
-#undef PROTOBUF_INTERNAL_TYPE_CARD_CASE
-
-  format(")");
-}
-
 void ParseFunctionGenerator::GenerateFieldEntries(Formatter& format) {
   for (const auto& entry : tc_table_info_->field_entries) {
     const FieldDescriptor* field = entry.field;
@@ -787,7 +652,9 @@
         format("0, ");
       }
       format("$1$,\n ", entry.aux_idx);
-      FormatFieldKind(format, entry);
+      // Use `0|` prefix to eagerly convert the enums to int to avoid enum-enum
+      // operations. They are deprecated in C++20.
+      format("(0 | $1$)", internal::TypeCardToString(entry.type_card));
     }
     format("},\n");
   }
diff --git a/src/google/protobuf/generated_message_tctable_decl.h b/src/google/protobuf/generated_message_tctable_decl.h
index 629cfb7..22aa54b 100644
--- a/src/google/protobuf/generated_message_tctable_decl.h
+++ b/src/google/protobuf/generated_message_tctable_decl.h
@@ -18,6 +18,7 @@
 #include <cstdint>
 #include <type_traits>
 
+#include "absl/types/span.h"
 #include "google/protobuf/message_lite.h"
 #include "google/protobuf/parse_context.h"
 
@@ -375,6 +376,9 @@
     return reinterpret_cast<const FieldEntry*>(
         reinterpret_cast<uintptr_t>(this) + field_entries_offset);
   }
+  absl::Span<const FieldEntry> field_entries() const {
+    return {field_entries_begin(), num_field_entries};
+  }
   FieldEntry* field_entries_begin() {
     return reinterpret_cast<FieldEntry*>(reinterpret_cast<uintptr_t>(this) +
                                          field_entries_offset);
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h
index 42a5608..fb111e3 100644
--- a/src/google/protobuf/generated_message_tctable_impl.h
+++ b/src/google/protobuf/generated_message_tctable_impl.h
@@ -943,6 +943,10 @@
   return ptr;
 }
 
+// Prints the type card as or of labels, using known higher level labels.
+// Used for code generation, but also useful for debugging.
+PROTOBUF_EXPORT std::string TypeCardToString(uint16_t type_card);
+
 }  // namespace internal
 }  // namespace protobuf
 }  // namespace google
diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc
index 104a22a..e37a208 100644
--- a/src/google/protobuf/generated_message_tctable_lite.cc
+++ b/src/google/protobuf/generated_message_tctable_lite.cc
@@ -18,6 +18,7 @@
 #include "absl/log/absl_check.h"
 #include "absl/log/absl_log.h"
 #include "absl/numeric/bits.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "google/protobuf/generated_message_tctable_decl.h"
 #include "google/protobuf/generated_message_tctable_impl.h"
@@ -2800,6 +2801,138 @@
   PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_NO_DATA_PASS);
 }
 
+std::string TypeCardToString(uint16_t type_card) {
+  // In here we convert the runtime value of entry.type_card back into a
+  // sequence of literal enum labels. We use the mnenonic labels for nicer
+  // codegen.
+  namespace fl = internal::field_layout;
+  const int rep_index = (type_card & fl::kRepMask) >> fl::kRepShift;
+  const int tv_index = (type_card & fl::kTvMask) >> fl::kTvShift;
+
+  static constexpr const char* kFieldCardNames[] = {"Singular", "Optional",
+                                                    "Repeated", "Oneof"};
+  static_assert((fl::kFcSingular >> fl::kFcShift) == 0, "");
+  static_assert((fl::kFcOptional >> fl::kFcShift) == 1, "");
+  static_assert((fl::kFcRepeated >> fl::kFcShift) == 2, "");
+  static_assert((fl::kFcOneof >> fl::kFcShift) == 3, "");
+
+  std::string out;
+
+  absl::StrAppend(&out, "::_fl::kFc",
+                  kFieldCardNames[(type_card & fl::kFcMask) >> fl::kFcShift]);
+
+#define PROTOBUF_INTERNAL_TYPE_CARD_CASE(x)  \
+  case fl::k##x:                             \
+    absl::StrAppend(&out, " | ::_fl::k" #x); \
+    break
+
+  switch (type_card & fl::kFkMask) {
+    case fl::kFkString: {
+      switch (type_card & ~fl::kFcMask & ~fl::kRepMask & ~fl::kSplitMask) {
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Bytes);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(RawString);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Utf8String);
+        default:
+          ABSL_LOG(FATAL) << "Unknown type_card: 0x" << type_card;
+      }
+
+      static constexpr const char* kRepNames[] = {"AString", "IString", "Cord",
+                                                  "SPiece", "SString"};
+      static_assert((fl::kRepAString >> fl::kRepShift) == 0, "");
+      static_assert((fl::kRepIString >> fl::kRepShift) == 1, "");
+      static_assert((fl::kRepCord >> fl::kRepShift) == 2, "");
+      static_assert((fl::kRepSPiece >> fl::kRepShift) == 3, "");
+      static_assert((fl::kRepSString >> fl::kRepShift) == 4, "");
+
+      absl::StrAppend(&out, " | ::_fl::kRep", kRepNames[rep_index]);
+      break;
+    }
+
+    case fl::kFkMessage: {
+      absl::StrAppend(&out, " | ::_fl::kMessage");
+
+      static constexpr const char* kRepNames[] = {nullptr, "Group", "Lazy"};
+      static_assert((fl::kRepGroup >> fl::kRepShift) == 1, "");
+      static_assert((fl::kRepLazy >> fl::kRepShift) == 2, "");
+
+      if (auto* rep = kRepNames[rep_index]) {
+        absl::StrAppend(&out, " | ::_fl::kRep", rep);
+      }
+
+      static constexpr const char* kXFormNames[2][4] = {
+          {nullptr, "Default", "Table", "WeakPtr"}, {nullptr, "Eager", "Lazy"}};
+
+      static_assert((fl::kTvDefault >> fl::kTvShift) == 1, "");
+      static_assert((fl::kTvTable >> fl::kTvShift) == 2, "");
+      static_assert((fl::kTvWeakPtr >> fl::kTvShift) == 3, "");
+      static_assert((fl::kTvEager >> fl::kTvShift) == 1, "");
+      static_assert((fl::kTvLazy >> fl::kTvShift) == 2, "");
+
+      if (auto* xform = kXFormNames[rep_index == 2][tv_index]) {
+        absl::StrAppend(&out, " | ::_fl::kTv", xform);
+      }
+      break;
+    }
+
+    case fl::kFkMap:
+      absl::StrAppend(&out, " | ::_fl::kMap");
+      break;
+
+    case fl::kFkNone:
+      break;
+
+    case fl::kFkVarint:
+    case fl::kFkPackedVarint:
+    case fl::kFkFixed:
+    case fl::kFkPackedFixed: {
+      switch (type_card & ~fl::kFcMask & ~fl::kSplitMask) {
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Bool);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Fixed32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(UInt32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SFixed32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Int32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SInt32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Float);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Enum);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(EnumRange);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(OpenEnum);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Fixed64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(UInt64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SFixed64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Int64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(SInt64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(Double);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedBool);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFixed32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedUInt32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSFixed32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedInt32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSInt32);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFloat);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedEnum);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedEnumRange);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedOpenEnum);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedFixed64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedUInt64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSFixed64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedInt64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedSInt64);
+        PROTOBUF_INTERNAL_TYPE_CARD_CASE(PackedDouble);
+        default:
+          ABSL_LOG(FATAL) << "Unknown type_card: 0x" << type_card;
+      }
+    }
+  }
+
+  if (type_card & fl::kSplitMask) {
+    absl::StrAppend(&out, " | ::_fl::kSplitTrue");
+  }
+
+#undef PROTOBUF_INTERNAL_TYPE_CARD_CASE
+
+  return out;
+}
+
 }  // namespace internal
 }  // namespace protobuf
 }  // namespace google
diff --git a/src/google/protobuf/generated_message_tctable_lite_test.cc b/src/google/protobuf/generated_message_tctable_lite_test.cc
index d037852..8648712 100644
--- a/src/google/protobuf/generated_message_tctable_lite_test.cc
+++ b/src/google/protobuf/generated_message_tctable_lite_test.cc
@@ -20,6 +20,7 @@
 
 namespace {
 
+using ::testing::ElementsAreArray;
 using ::testing::Eq;
 using ::testing::Not;
 using ::testing::Optional;
@@ -899,6 +900,7 @@
   EXPECT_LE(proto.vals().Capacity(), 2048);
 }
 
+
 }  // namespace internal
 }  // namespace protobuf
 }  // namespace google