ftl: add enum_string_full which includes the enum class name

Bug: 300701739
Test: presubmit
Change-Id: I235275b5d299efea779851434c392ca4e07ca4fd
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 075d12b..2c86e2e 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -25,12 +25,12 @@
 
 #include <ftl/string.h>
 
-// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the
-// compiler-generated string literal for the signature of this function. The function is defined in
-// the global namespace with a short name and inferred return type to reduce bloat in the read-only
-// data segment.
-template <typename E, E V>
-constexpr auto ftl_enum() {
+// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as
+// std::optional<std::string_view> by parsing the compiler-generated string literal for the
+// signature of this function. The function is defined in the global namespace with a short name
+// and inferred return type to reduce bloat in the read-only data segment.
+template <bool S, typename E, E V>
+constexpr auto ftl_enum_builder() {
   static_assert(std::is_enum_v<E>);
 
   using R = std::optional<std::string_view>;
@@ -58,7 +58,9 @@
   //   V = android::test::Enum::kValue
   //
   view = view.substr(value_begin);
-  const auto name_begin = view.rfind("::"sv);
+  const auto pos = S ? view.rfind("::"sv) - 2 : view.npos;
+
+  const auto name_begin = view.rfind("::"sv, pos);
   if (name_begin == view.npos) return R{};
 
   // Chop off the leading "::".
@@ -68,6 +70,18 @@
   return name.find(')') == view.npos ? R{name} : R{};
 }
 
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum() {
+  return ftl_enum_builder<false, E, V>();
+}
+
+// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum_full() {
+  return ftl_enum_builder<true, E, V>();
+}
+
 namespace android::ftl {
 
 // Trait for determining whether a type is specifically a scoped enum or not. By definition, a
@@ -191,6 +205,11 @@
   static constexpr auto value = ftl_enum<decltype(V), V>();
 };
 
+template <auto V>
+struct EnumNameFull {
+  static constexpr auto value = ftl_enum_full<decltype(V), V>();
+};
+
 template <auto I>
 struct FlagName {
   using E = decltype(I);
@@ -230,6 +249,18 @@
   return *kName;
 }
 
+// Returns a stringified enumerator with class at compile time.
+//
+//   enum class E { A, B, C };
+//   static_assert(ftl::enum_name<E::B>() == "E::B");
+//
+template <auto V>
+constexpr std::string_view enum_name_full() {
+  constexpr auto kName = ftl_enum_full<decltype(V), V>();
+  static_assert(kName, "Unknown enumerator");
+  return *kName;
+}
+
 // Returns a stringified enumerator, possibly at compile time.
 //
 //   enum class E { A, B, C, F = 5, ftl_last = F };
@@ -249,6 +280,25 @@
   return kRange.values[value - kBegin];
 }
 
+// Returns a stringified enumerator with class, possibly at compile time.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   static_assert(ftl::enum_name(E::C).value_or("?") == "E::C");
+//   static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> enum_name_full(E v) {
+  const auto value = to_underlying(v);
+
+  constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  constexpr auto kLast = to_underlying(enum_last_v<E>);
+  if (value < kBegin || value > kLast) return {};
+
+  constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{};
+  return kRange.values[value - kBegin];
+}
+
 // Returns a stringified flag enumerator, possibly at compile time.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
@@ -282,6 +332,21 @@
   return to_string(to_underlying(v));
 }
 
+// Returns a stringified enumerator with class, or its integral value if not named.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   assert(ftl::enum_string(E::C) == "E::C");
+//   assert(ftl::enum_string(E{3}) == "3");
+//
+template <typename E>
+inline std::string enum_string_full(E v) {
+  if (const auto name = enum_name_full(v)) {
+      return std::string(*name);
+  }
+  return to_string(to_underlying(v));
+}
+
 // Returns a stringified flag enumerator, or its integral value if not named.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01..b68c2c3 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@
 static_assert(ftl::enum_name(E::C).value_or("?") == "C");
 static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
 
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
 enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
 
 static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@
 static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
 static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
 
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
 // Though not flags, the enumerators are within the implicit range of bit indices.
 enum class Planet : std::uint8_t {
   kMercury,
@@ -81,6 +90,9 @@
 static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
 static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
 
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
 // Unscoped enum must define explicit range, even if the underlying type is fixed.
 enum Temperature : int {
   kRoom = 20,
@@ -122,16 +134,28 @@
     EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
   }
   {
     EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+    EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+    EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
   }
 }
 
@@ -158,16 +182,30 @@
     EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+    EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
   }
   {
     EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+    EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+    EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
   }
 }