Union has its associated (nested) tag enum

For each union, a `Tag` enum type is auto-generated.

  union U {
   int a;
   String b;
  }

is equivalent to

  union U {
    int a;
    String b;
    @Backing(type="int") enum Tag { a, b }
  }

Bug: 218912230
Test: aidl_integration_test
Change-Id: Ic0cb8f4ad9a80fe9a301a97d4f061457fc99756a
diff --git a/aidl_language.h b/aidl_language.h
index f3c5829..15ce4bb 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -999,10 +999,6 @@
     return constants_;
   }
   const std::vector<std::unique_ptr<AidlMethod>>& GetMethods() const { return methods_; }
-  void AddMethod(std::unique_ptr<AidlMethod> method) {
-    members_.push_back(method.get());
-    methods_.push_back(std::move(method));
-  }
   const std::vector<const AidlMember*>& GetMembers() const { return members_; }
   void TraverseChildren(std::function<void(const AidlNode&)> traverse) const override {
     AidlAnnotatable::TraverseChildren(traverse);
@@ -1011,6 +1007,17 @@
     }
   }
 
+  // Modifiers
+  void AddMethod(std::unique_ptr<AidlMethod> method) {
+    members_.push_back(method.get());
+    methods_.push_back(std::move(method));
+  }
+  void AddType(std::unique_ptr<AidlDefinedType> type) {
+    type->SetEnclosingScope(this);
+    members_.push_back(type.get());
+    types_.push_back(std::move(type));
+  }
+
  protected:
   // utility for subclasses with getter names
   bool CheckValidForGetterNames() const;
diff --git a/aidl_to_cpp_common.cpp b/aidl_to_cpp_common.cpp
index b8e38ea..030b13e 100644
--- a/aidl_to_cpp_common.cpp
+++ b/aidl_to_cpp_common.cpp
@@ -481,7 +481,7 @@
     const auto& default_value = name_of(first_field->GetType(), typenames) + "(" +
                                 first_field->ValueString(decorator) + ")";
 
-    out << "Tag _tag __attribute__((aligned (1))) = " << default_name << ";\n";
+    out << "Tag _tag = " << default_name << ";\n";
     out << "union _value_t {\n";
     out.Indent();
     out << "_value_t() {}\n";
@@ -512,23 +512,12 @@
 }
 
 void UnionWriter::PublicFields(CodeWriter& out) const {
-  std::string tag_type = "int32_t";
-  if (decl.IsFixedSize()) {
-    // For @FixedSize union, we use a smaller type for a tag to minimize the size overhead.
-    AIDL_FATAL_IF(decl.GetFields().size() > std::numeric_limits<uint8_t>::max(), decl)
-        << "Too many fields for @FixedSize";
-    tag_type = "uint8_t";
-  }
-  out << "enum Tag : " << tag_type << " {\n";
-  bool is_first = true;
+  out << "// Expose tag symbols for legacy code\n";
   for (const auto& f : decl.GetFields()) {
-    out << "  " << f->GetName();
+    out << "static const inline Tag";
     GenerateDeprecated(out, *f);
-    if (is_first) out << " = 0";
-    out << ",  // " << f->Signature() << ";\n";
-    is_first = false;
+    out << " " << f->GetName() << " = Tag::" << f->GetName() << ";\n";
   }
-  out << "};\n";
 
   const auto& name = decl.GetName();
 
diff --git a/parser.cpp b/parser.cpp
index 9988f56..edb110b 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -28,6 +28,29 @@
 YY_BUFFER_STATE yy_scan_buffer(char*, size_t, void*);
 void yy_delete_buffer(YY_BUFFER_STATE, void*);
 
+// For each union, generate nested "Tag" enum type so that "Tag" can be used as a valid type.
+//    union Foo { int a; int b; } => union Foo { ... enum Tag { a, b }}
+struct UnionTagGenerater : AidlVisitor {
+  void Visit(const AidlUnionDecl& decl) override {
+    std::vector<std::unique_ptr<AidlEnumerator>> enumerators;
+    for (const auto& field : decl.GetFields()) {
+      enumerators.push_back(std::make_unique<AidlEnumerator>(AIDL_LOCATION_HERE, field->GetName(),
+                                                             nullptr, field->GetComments()));
+    }
+    auto tag_enum = std::make_unique<AidlEnumDeclaration>(AIDL_LOCATION_HERE, "Tag", &enumerators,
+                                                          decl.GetPackage(), Comments{});
+    // Tag for @FixedSize union is limited to "byte" type so that it can be passed via FMQ with
+    // with lower overhead.
+    std::shared_ptr<AidlConstantValue> backing_type{
+        AidlConstantValue::String(AIDL_LOCATION_HERE, decl.IsFixedSize() ? "\"byte\"" : "\"int\"")};
+    std::vector<std::unique_ptr<AidlAnnotation>> annotations;
+    annotations.push_back(
+        AidlAnnotation::Parse(AIDL_LOCATION_HERE, "Backing", {{"type", backing_type}}, Comments{}));
+    tag_enum->Annotate(std::move(annotations));
+    const_cast<AidlUnionDecl&>(decl).AddType(std::move(tag_enum));
+  }
+};
+
 const AidlDocument* Parser::Parse(const std::string& filename,
                                   const android::aidl::IoDelegate& io_delegate,
                                   AidlTypenames& typenames, bool is_preprocessed) {
@@ -55,6 +78,10 @@
     return nullptr;
   }
 
+  // Preprocess parsed document before adding to typenames.
+  UnionTagGenerater v;
+  VisitTopDown(v, *parser.document_);
+
   // transfer ownership to AidlTypenames and return the raw pointer
   const AidlDocument* result = parser.document_.get();
   if (!typenames.AddDocument(std::move(parser.document_))) {
diff --git a/tests/aidl_test_client_ndk_parcelables.cpp b/tests/aidl_test_client_ndk_parcelables.cpp
index 72d309d..8b9cd19 100644
--- a/tests/aidl_test_client_ndk_parcelables.cpp
+++ b/tests/aidl_test_client_ndk_parcelables.cpp
@@ -25,6 +25,7 @@
 #include <aidl/android/aidl/fixedsizearray/FixedSizeArrayExample.h>
 #include <aidl/android/aidl/tests/ITestService.h>
 #include <aidl/android/aidl/tests/RecursiveList.h>
+#include <aidl/android/aidl/tests/Union.h>
 
 using aidl::android::aidl::fixedsizearray::FixedSizeArrayExample;
 using BnRepeatFixedSizeArray =
@@ -39,6 +40,7 @@
 using aidl::android::aidl::tests::BackendType;
 using aidl::android::aidl::tests::ITestService;
 using aidl::android::aidl::tests::RecursiveList;
+using aidl::android::aidl::tests::Union;
 using android::OK;
 using ndk::AParcel_readData;
 using ndk::AParcel_writeData;
@@ -79,6 +81,21 @@
   EXPECT_EQ(nullptr, cur);
 }
 
+TEST_F(AidlTest, GetUnionTags) {
+  std::vector<Union> unions;
+  std::vector<Union::Tag> tags;
+  // test empty
+  auto status = getService<ITestService>()->GetUnionTags(unions, &tags);
+  ASSERT_TRUE(status.isOk());
+  EXPECT_EQ(tags, (std::vector<Union::Tag>{}));
+  // test non-empty
+  unions.push_back(Union::make<Union::n>());
+  unions.push_back(Union::make<Union::ns>());
+  status = getService<ITestService>()->GetUnionTags(unions, &tags);
+  ASSERT_TRUE(status.isOk());
+  EXPECT_EQ(tags, (std::vector<Union::Tag>{Union::n, Union::ns}));
+}
+
 TEST_F(AidlTest, FixedSizeArray) {
   auto parcel = AParcel_create();
 
diff --git a/tests/aidl_test_client_parcelables.cpp b/tests/aidl_test_client_parcelables.cpp
index 8d4adee..119f513 100644
--- a/tests/aidl_test_client_parcelables.cpp
+++ b/tests/aidl_test_client_parcelables.cpp
@@ -575,6 +575,21 @@
   EXPECT_EQ(nullptr, cur);
 }
 
+TEST_F(AidlTest, GetUnionTags) {
+  std::vector<Union> unions;
+  std::vector<Union::Tag> tags;
+  // test empty
+  auto status = service->GetUnionTags(unions, &tags);
+  ASSERT_TRUE(status.isOk()) << status.toString8();
+  EXPECT_EQ(tags, (std::vector<Union::Tag>{}));
+  // test non-empty
+  unions.push_back(Union::make<Union::n>());
+  unions.push_back(Union::make<Union::ns>());
+  status = service->GetUnionTags(unions, &tags);
+  ASSERT_TRUE(status.isOk()) << status.toString8();
+  EXPECT_EQ(tags, (std::vector<Union::Tag>{Union::n, Union::ns}));
+}
+
 TEST_F(AidlTest, FixedSizeArray) {
   android::Parcel parcel;
 
diff --git a/tests/aidl_test_service.cpp b/tests/aidl_test_service.cpp
index f000372..a13547f 100644
--- a/tests/aidl_test_service.cpp
+++ b/tests/aidl_test_service.cpp
@@ -742,6 +742,15 @@
     return Status::ok();
   }
 
+  Status GetUnionTags(const std::vector<Union>& input,
+                      std::vector<Union::Tag>* _aidl_return) override {
+    std::vector<Union::Tag> tags;
+    std::transform(input.begin(), input.end(), std::back_inserter(tags),
+                   std::mem_fn(&Union::getTag));
+    *_aidl_return = std::move(tags);
+    return Status::ok();
+  }
+
   Status GetCppJavaTests(sp<IBinder>* ret) {
     *ret = new CppJavaTests;
     return Status::ok();
diff --git a/tests/android/aidl/tests/ITestService.aidl b/tests/android/aidl/tests/ITestService.aidl
index f8e168a..63c0c6c 100644
--- a/tests/android/aidl/tests/ITestService.aidl
+++ b/tests/android/aidl/tests/ITestService.aidl
@@ -25,6 +25,7 @@
 import android.aidl.tests.LongEnum;
 import android.aidl.tests.RecursiveList;
 import android.aidl.tests.StructuredParcelable;
+import android.aidl.tests.Union;
 import android.aidl.tests.extension.ExtendableParcelable;
 
 /**
@@ -259,6 +260,8 @@
     IOldName GetOldNameInterface();
     INewName GetNewNameInterface();
 
+    Union.Tag[] GetUnionTags(in Union[] input);
+
     // Retrieve the ICppJavaTests if the server supports it
     @nullable IBinder GetCppJavaTests();
 
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp b/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp
index 40264c0..b7e11ae 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp
@@ -2527,6 +2527,44 @@
   return _aidl_status;
 }
 
+::android::binder::Status BpTestService::GetUnionTags(const ::std::vector<::android::aidl::tests::Union>& input, ::std::vector<::android::aidl::tests::Union::Tag>* _aidl_return) {
+  ::android::Parcel _aidl_data;
+  _aidl_data.markSensitive();
+  _aidl_data.markForBinder(remoteStrong());
+  ::android::Parcel _aidl_reply;
+  ::android::status_t _aidl_ret_status = ::android::OK;
+  ::android::binder::Status _aidl_status;
+  _aidl_ret_status = _aidl_data.writeInterfaceToken(getInterfaceDescriptor());
+  if (((_aidl_ret_status) != (::android::OK))) {
+    goto _aidl_error;
+  }
+  _aidl_ret_status = _aidl_data.writeParcelableVector(input);
+  if (((_aidl_ret_status) != (::android::OK))) {
+    goto _aidl_error;
+  }
+  _aidl_ret_status = remote()->transact(BnTestService::TRANSACTION_GetUnionTags, _aidl_data, &_aidl_reply, ::android::IBinder::FLAG_CLEAR_BUF);
+  if (UNLIKELY(_aidl_ret_status == ::android::UNKNOWN_TRANSACTION && ITestService::getDefaultImpl())) {
+     return ITestService::getDefaultImpl()->GetUnionTags(input, _aidl_return);
+  }
+  if (((_aidl_ret_status) != (::android::OK))) {
+    goto _aidl_error;
+  }
+  _aidl_ret_status = _aidl_status.readFromParcel(_aidl_reply);
+  if (((_aidl_ret_status) != (::android::OK))) {
+    goto _aidl_error;
+  }
+  if (!_aidl_status.isOk()) {
+    return _aidl_status;
+  }
+  _aidl_ret_status = _aidl_reply.readEnumVector(_aidl_return);
+  if (((_aidl_ret_status) != (::android::OK))) {
+    goto _aidl_error;
+  }
+  _aidl_error:
+  _aidl_status.setFromStatusT(_aidl_ret_status);
+  return _aidl_status;
+}
+
 ::android::binder::Status BpTestService::GetCppJavaTests(::android::sp<::android::IBinder>* _aidl_return) {
   ::android::Parcel _aidl_data;
   _aidl_data.markSensitive();
@@ -4602,6 +4640,36 @@
     }
   }
   break;
+  case BnTestService::TRANSACTION_GetUnionTags:
+  {
+    ::std::vector<::android::aidl::tests::Union> in_input;
+    ::std::vector<::android::aidl::tests::Union::Tag> _aidl_return;
+    if (!(_aidl_data.checkInterface(this))) {
+      _aidl_ret_status = ::android::BAD_TYPE;
+      break;
+    }
+    _aidl_ret_status = _aidl_data.readParcelableVector(&in_input);
+    if (((_aidl_ret_status) != (::android::OK))) {
+      break;
+    }
+    if (auto st = _aidl_data.enforceNoDataAvail(); !st.isOk()) {
+      _aidl_ret_status = st.writeToParcel(_aidl_reply);
+      break;
+    }
+    ::android::binder::Status _aidl_status(GetUnionTags(in_input, &_aidl_return));
+    _aidl_ret_status = _aidl_status.writeToParcel(_aidl_reply);
+    if (((_aidl_ret_status) != (::android::OK))) {
+      break;
+    }
+    if (!_aidl_status.isOk()) {
+      break;
+    }
+    _aidl_ret_status = _aidl_reply->writeEnumVector(_aidl_return);
+    if (((_aidl_ret_status) != (::android::OK))) {
+      break;
+    }
+  }
+  break;
   case BnTestService::TRANSACTION_GetCppJavaTests:
   {
     ::android::sp<::android::IBinder> _aidl_return;
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp.d b/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp.d
index f82081e..de66202 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp.d
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/android/aidl/tests/ITestService.cpp.d
@@ -9,6 +9,6 @@
   system/tools/aidl/tests/android/aidl/tests/LongEnum.aidl \
   system/tools/aidl/tests/android/aidl/tests/RecursiveList.aidl \
   system/tools/aidl/tests/android/aidl/tests/StructuredParcelable.aidl \
+  system/tools/aidl/tests/android/aidl/tests/Union.aidl \
   system/tools/aidl/tests/android/aidl/tests/extension/ExtendableParcelable.aidl \
-  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl \
-  system/tools/aidl/tests/android/aidl/tests/Union.aidl
+  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ArrayOfInterfaces.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ArrayOfInterfaces.h
index 0265063..b5c0a6c 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ArrayOfInterfaces.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ArrayOfInterfaces.h
@@ -2,12 +2,16 @@
 
 #include <android/aidl/tests/ArrayOfInterfaces.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
+#include <cstdint>
 #include <optional>
+#include <string>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -136,12 +140,17 @@
   };  // class MyParcelable
   class MyUnion : public ::android::Parcelable {
   public:
-    enum Tag : int32_t {
-      iface = 0,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface iface;
-      nullable_iface,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface nullable_iface;
-      iface_array,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface[] iface_array;
-      nullable_iface_array,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface[] nullable_iface_array;
+    enum class Tag : int32_t {
+      iface = 0,
+      nullable_iface = 1,
+      iface_array = 2,
+      nullable_iface_array = 3,
     };
+    // Expose tag symbols for legacy code
+    static const inline Tag iface = Tag::iface;
+    static const inline Tag nullable_iface = Tag::nullable_iface;
+    static const inline Tag iface_array = Tag::iface_array;
+    static const inline Tag nullable_iface_array = Tag::nullable_iface_array;
 
     template<typename _Tp>
     static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, MyUnion>;
@@ -263,3 +272,37 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(ArrayOfInterfaces::MyUnion::Tag val) {
+  switch(val) {
+  case ArrayOfInterfaces::MyUnion::Tag::iface:
+    return "iface";
+  case ArrayOfInterfaces::MyUnion::Tag::nullable_iface:
+    return "nullable_iface";
+  case ArrayOfInterfaces::MyUnion::Tag::iface_array:
+    return "iface_array";
+  case ArrayOfInterfaces::MyUnion::Tag::nullable_iface_array:
+    return "nullable_iface_array";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag, 4> enum_values<::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag> = {
+  ::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::iface,
+  ::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::nullable_iface,
+  ::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::iface_array,
+  ::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::nullable_iface_array,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BnTestService.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BnTestService.h
index 2d2a864..3cb8ad0 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BnTestService.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BnTestService.h
@@ -71,8 +71,9 @@
   static constexpr uint32_t TRANSACTION_ReverseNullableIBinderArray = ::android::IBinder::FIRST_CALL_TRANSACTION + 60;
   static constexpr uint32_t TRANSACTION_GetOldNameInterface = ::android::IBinder::FIRST_CALL_TRANSACTION + 61;
   static constexpr uint32_t TRANSACTION_GetNewNameInterface = ::android::IBinder::FIRST_CALL_TRANSACTION + 62;
-  static constexpr uint32_t TRANSACTION_GetCppJavaTests = ::android::IBinder::FIRST_CALL_TRANSACTION + 63;
-  static constexpr uint32_t TRANSACTION_getBackendType = ::android::IBinder::FIRST_CALL_TRANSACTION + 64;
+  static constexpr uint32_t TRANSACTION_GetUnionTags = ::android::IBinder::FIRST_CALL_TRANSACTION + 63;
+  static constexpr uint32_t TRANSACTION_GetCppJavaTests = ::android::IBinder::FIRST_CALL_TRANSACTION + 64;
+  static constexpr uint32_t TRANSACTION_getBackendType = ::android::IBinder::FIRST_CALL_TRANSACTION + 65;
   explicit BnTestService();
   ::android::status_t onTransact(uint32_t _aidl_code, const ::android::Parcel& _aidl_data, ::android::Parcel* _aidl_reply, uint32_t _aidl_flags) override;
 };  // class BnTestService
@@ -270,6 +271,9 @@
   ::android::binder::Status GetNewNameInterface(::android::sp<::android::aidl::tests::INewName>* _aidl_return) override {
     return _aidl_delegate->GetNewNameInterface(_aidl_return);
   }
+  ::android::binder::Status GetUnionTags(const ::std::vector<::android::aidl::tests::Union>& input, ::std::vector<::android::aidl::tests::Union::Tag>* _aidl_return) override {
+    return _aidl_delegate->GetUnionTags(input, _aidl_return);
+  }
   ::android::binder::Status GetCppJavaTests(::android::sp<::android::IBinder>* _aidl_return) override {
     return _aidl_delegate->GetCppJavaTests(_aidl_return);
   }
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BpTestService.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BpTestService.h
index c1cbaa8..3829d9b 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BpTestService.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/BpTestService.h
@@ -75,6 +75,7 @@
   ::android::binder::Status ReverseNullableIBinderArray(const ::std::optional<::std::vector<::android::sp<::android::IBinder>>>& input, ::std::optional<::std::vector<::android::sp<::android::IBinder>>>* repeated, ::std::optional<::std::vector<::android::sp<::android::IBinder>>>* _aidl_return) override;
   ::android::binder::Status GetOldNameInterface(::android::sp<::android::aidl::tests::IOldName>* _aidl_return) override;
   ::android::binder::Status GetNewNameInterface(::android::sp<::android::aidl::tests::INewName>* _aidl_return) override;
+  ::android::binder::Status GetUnionTags(const ::std::vector<::android::aidl::tests::Union>& input, ::std::vector<::android::aidl::tests::Union::Tag>* _aidl_return) override;
   ::android::binder::Status GetCppJavaTests(::android::sp<::android::IBinder>* _aidl_return) override;
   ::android::binder::Status getBackendType(::android::aidl::tests::BackendType* _aidl_return) override;
 };  // class BpTestService
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/FixedSize.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/FixedSize.h
index f94a3b8..65f6711 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/FixedSize.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/FixedSize.h
@@ -3,10 +3,13 @@
 #include <android/aidl/tests/FixedSize.h>
 #include <android/aidl/tests/LongEnum.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
 #include <cstdint>
+#include <string>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -24,16 +27,25 @@
 public:
   class FixedUnion : public ::android::Parcelable {
   public:
-    enum Tag : uint8_t {
-      booleanValue = 0,  // boolean booleanValue;
-      byteValue,  // byte byteValue;
-      charValue,  // char charValue;
-      intValue,  // int intValue;
-      longValue,  // long longValue;
-      floatValue,  // float floatValue;
-      doubleValue,  // double doubleValue;
-      enumValue,  // android.aidl.tests.LongEnum enumValue;
+    enum class Tag : int8_t {
+      booleanValue = 0,
+      byteValue = 1,
+      charValue = 2,
+      intValue = 3,
+      longValue = 4,
+      floatValue = 5,
+      doubleValue = 6,
+      enumValue = 7,
     };
+    // Expose tag symbols for legacy code
+    static const inline Tag booleanValue = Tag::booleanValue;
+    static const inline Tag byteValue = Tag::byteValue;
+    static const inline Tag charValue = Tag::charValue;
+    static const inline Tag intValue = Tag::intValue;
+    static const inline Tag longValue = Tag::longValue;
+    static const inline Tag floatValue = Tag::floatValue;
+    static const inline Tag doubleValue = Tag::doubleValue;
+    static const inline Tag enumValue = Tag::enumValue;
 
     template <Tag _Tag>
     using _at = typename std::tuple_element<static_cast<size_t>(_Tag), std::tuple<bool, int8_t, char16_t, int32_t, int64_t, float, double, ::android::aidl::tests::LongEnum>>::type;
@@ -119,7 +131,7 @@
       return os.str();
     }
   private:
-    Tag _tag __attribute__((aligned (1))) = booleanValue;
+    Tag _tag = booleanValue;
     union _value_t {
       _value_t() {}
       ~_value_t() {}
@@ -220,3 +232,49 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(FixedSize::FixedUnion::Tag val) {
+  switch(val) {
+  case FixedSize::FixedUnion::Tag::booleanValue:
+    return "booleanValue";
+  case FixedSize::FixedUnion::Tag::byteValue:
+    return "byteValue";
+  case FixedSize::FixedUnion::Tag::charValue:
+    return "charValue";
+  case FixedSize::FixedUnion::Tag::intValue:
+    return "intValue";
+  case FixedSize::FixedUnion::Tag::longValue:
+    return "longValue";
+  case FixedSize::FixedUnion::Tag::floatValue:
+    return "floatValue";
+  case FixedSize::FixedUnion::Tag::doubleValue:
+    return "doubleValue";
+  case FixedSize::FixedUnion::Tag::enumValue:
+    return "enumValue";
+  default:
+    return std::to_string(static_cast<int8_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::FixedSize::FixedUnion::Tag, 8> enum_values<::android::aidl::tests::FixedSize::FixedUnion::Tag> = {
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::booleanValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::byteValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::charValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::intValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::longValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::floatValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::doubleValue,
+  ::android::aidl::tests::FixedSize::FixedUnion::Tag::enumValue,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ITestService.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ITestService.h
index dd0e7f1..9246a87 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ITestService.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ITestService.h
@@ -10,6 +10,7 @@
 #include <android/aidl/tests/LongEnum.h>
 #include <android/aidl/tests/RecursiveList.h>
 #include <android/aidl/tests/StructuredParcelable.h>
+#include <android/aidl/tests/Union.h>
 #include <android/aidl/tests/extension/ExtendableParcelable.h>
 #include <android/binder_to_string.h>
 #include <binder/IBinder.h>
@@ -272,6 +273,7 @@
   virtual ::android::binder::Status ReverseNullableIBinderArray(const ::std::optional<::std::vector<::android::sp<::android::IBinder>>>& input, ::std::optional<::std::vector<::android::sp<::android::IBinder>>>* repeated, ::std::optional<::std::vector<::android::sp<::android::IBinder>>>* _aidl_return) = 0;
   virtual ::android::binder::Status GetOldNameInterface(::android::sp<::android::aidl::tests::IOldName>* _aidl_return) = 0;
   virtual ::android::binder::Status GetNewNameInterface(::android::sp<::android::aidl::tests::INewName>* _aidl_return) = 0;
+  virtual ::android::binder::Status GetUnionTags(const ::std::vector<::android::aidl::tests::Union>& input, ::std::vector<::android::aidl::tests::Union::Tag>* _aidl_return) = 0;
   virtual ::android::binder::Status GetCppJavaTests(::android::sp<::android::IBinder>* _aidl_return) = 0;
   virtual ::android::binder::Status getBackendType(::android::aidl::tests::BackendType* _aidl_return) = 0;
 };  // class ITestService
@@ -470,6 +472,9 @@
   ::android::binder::Status GetNewNameInterface(::android::sp<::android::aidl::tests::INewName>* /*_aidl_return*/) override {
     return ::android::binder::Status::fromStatusT(::android::UNKNOWN_TRANSACTION);
   }
+  ::android::binder::Status GetUnionTags(const ::std::vector<::android::aidl::tests::Union>& /*input*/, ::std::vector<::android::aidl::tests::Union::Tag>* /*_aidl_return*/) override {
+    return ::android::binder::Status::fromStatusT(::android::UNKNOWN_TRANSACTION);
+  }
   ::android::binder::Status GetCppJavaTests(::android::sp<::android::IBinder>* /*_aidl_return*/) override {
     return ::android::binder::Status::fromStatusT(::android::UNKNOWN_TRANSACTION);
   }
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ListOfInterfaces.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ListOfInterfaces.h
index e9dcb2b..455bf4b 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ListOfInterfaces.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/ListOfInterfaces.h
@@ -2,12 +2,16 @@
 
 #include <android/aidl/tests/ListOfInterfaces.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
+#include <cstdint>
 #include <optional>
+#include <string>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -136,12 +140,17 @@
   };  // class MyParcelable
   class MyUnion : public ::android::Parcelable {
   public:
-    enum Tag : int32_t {
-      iface = 0,  // android.aidl.tests.ListOfInterfaces.IEmptyInterface iface;
-      nullable_iface,  // android.aidl.tests.ListOfInterfaces.IEmptyInterface nullable_iface;
-      iface_list,  // List<android.aidl.tests.ListOfInterfaces.IEmptyInterface> iface_list;
-      nullable_iface_list,  // List<android.aidl.tests.ListOfInterfaces.IEmptyInterface> nullable_iface_list;
+    enum class Tag : int32_t {
+      iface = 0,
+      nullable_iface = 1,
+      iface_list = 2,
+      nullable_iface_list = 3,
     };
+    // Expose tag symbols for legacy code
+    static const inline Tag iface = Tag::iface;
+    static const inline Tag nullable_iface = Tag::nullable_iface;
+    static const inline Tag iface_list = Tag::iface_list;
+    static const inline Tag nullable_iface_list = Tag::nullable_iface_list;
 
     template<typename _Tp>
     static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, MyUnion>;
@@ -263,3 +272,37 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(ListOfInterfaces::MyUnion::Tag val) {
+  switch(val) {
+  case ListOfInterfaces::MyUnion::Tag::iface:
+    return "iface";
+  case ListOfInterfaces::MyUnion::Tag::nullable_iface:
+    return "nullable_iface";
+  case ListOfInterfaces::MyUnion::Tag::iface_list:
+    return "iface_list";
+  case ListOfInterfaces::MyUnion::Tag::nullable_iface_list:
+    return "nullable_iface_list";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::ListOfInterfaces::MyUnion::Tag, 4> enum_values<::android::aidl::tests::ListOfInterfaces::MyUnion::Tag> = {
+  ::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::iface,
+  ::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::nullable_iface,
+  ::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::iface_list,
+  ::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::nullable_iface_list,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/Union.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/Union.h
index 4428401..a82300f 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/Union.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/Union.h
@@ -2,6 +2,8 @@
 
 #include <android/aidl/tests/ByteEnum.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/IBinder.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
@@ -23,15 +25,23 @@
 namespace tests {
 class Union : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    ns = 0,  // int[] ns;
-    n,  // int n;
-    m,  // int m;
-    s,  // String s;
-    ibinder,  // IBinder ibinder;
-    ss,  // List<String> ss;
-    be,  // android.aidl.tests.ByteEnum be;
+  enum class Tag : int32_t {
+    ns = 0,
+    n = 1,
+    m = 2,
+    s = 3,
+    ibinder = 4,
+    ss = 5,
+    be = 6,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag ns = Tag::ns;
+  static const inline Tag n = Tag::n;
+  static const inline Tag m = Tag::m;
+  static const inline Tag s = Tag::s;
+  static const inline Tag ibinder = Tag::ibinder;
+  static const inline Tag ss = Tag::ss;
+  static const inline Tag be = Tag::be;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, Union>;
@@ -125,3 +135,46 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(Union::Tag val) {
+  switch(val) {
+  case Union::Tag::ns:
+    return "ns";
+  case Union::Tag::n:
+    return "n";
+  case Union::Tag::m:
+    return "m";
+  case Union::Tag::s:
+    return "s";
+  case Union::Tag::ibinder:
+    return "ibinder";
+  case Union::Tag::ss:
+    return "ss";
+  case Union::Tag::be:
+    return "be";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::Union::Tag, 7> enum_values<::android::aidl::tests::Union::Tag> = {
+  ::android::aidl::tests::Union::Tag::ns,
+  ::android::aidl::tests::Union::Tag::n,
+  ::android::aidl::tests::Union::Tag::m,
+  ::android::aidl::tests::Union::Tag::s,
+  ::android::aidl::tests::Union::Tag::ibinder,
+  ::android::aidl::tests::Union::Tag::ss,
+  ::android::aidl::tests::Union::Tag::be,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/UnionWithFd.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/UnionWithFd.h
index 06ae2a6..c97edf1 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/UnionWithFd.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/UnionWithFd.h
@@ -1,11 +1,14 @@
 #pragma once
 
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/ParcelFileDescriptor.h>
 #include <binder/Status.h>
 #include <cassert>
 #include <cstdint>
+#include <string>
 #include <type_traits>
 #include <utility>
 #include <utils/String16.h>
@@ -20,10 +23,13 @@
 namespace tests {
 class UnionWithFd : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    num = 0,  // int num;
-    pfd,  // ParcelFileDescriptor pfd;
+  enum class Tag : int32_t {
+    num = 0,
+    pfd = 1,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag num = Tag::num;
+  static const inline Tag pfd = Tag::pfd;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, UnionWithFd>;
@@ -111,3 +117,31 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(UnionWithFd::Tag val) {
+  switch(val) {
+  case UnionWithFd::Tag::num:
+    return "num";
+  case UnionWithFd::Tag::pfd:
+    return "pfd";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::UnionWithFd::Tag, 2> enum_values<::android::aidl::tests::UnionWithFd::Tag> = {
+  ::android::aidl::tests::UnionWithFd::Tag::num,
+  ::android::aidl::tests::UnionWithFd::Tag::pfd,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/EnumUnion.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/EnumUnion.h
index 3898fbc..a65f9f0 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/EnumUnion.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/EnumUnion.h
@@ -3,9 +3,13 @@
 #include <android/aidl/tests/IntEnum.h>
 #include <android/aidl/tests/LongEnum.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
+#include <cstdint>
+#include <string>
 #include <type_traits>
 #include <utility>
 #include <utils/String16.h>
@@ -21,10 +25,13 @@
 namespace unions {
 class EnumUnion : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    intEnum = 0,  // android.aidl.tests.IntEnum intEnum;
-    longEnum,  // android.aidl.tests.LongEnum longEnum;
+  enum class Tag : int32_t {
+    intEnum = 0,
+    longEnum = 1,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag intEnum = Tag::intEnum;
+  static const inline Tag longEnum = Tag::longEnum;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, EnumUnion>;
@@ -113,3 +120,33 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+namespace unions {
+[[nodiscard]] static inline std::string toString(EnumUnion::Tag val) {
+  switch(val) {
+  case EnumUnion::Tag::intEnum:
+    return "intEnum";
+  case EnumUnion::Tag::longEnum:
+    return "longEnum";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace unions
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::unions::EnumUnion::Tag, 2> enum_values<::android::aidl::tests::unions::EnumUnion::Tag> = {
+  ::android::aidl::tests::unions::EnumUnion::Tag::intEnum,
+  ::android::aidl::tests::unions::EnumUnion::Tag::longEnum,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/UnionInUnion.h b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/UnionInUnion.h
index 9adbbe9..9badc3a 100644
--- a/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/UnionInUnion.h
+++ b/tests/golden_output/aidl-test-interface-cpp-source/gen/include/android/aidl/tests/unions/UnionInUnion.h
@@ -2,10 +2,13 @@
 
 #include <android/aidl/tests/unions/EnumUnion.h>
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
 #include <cstdint>
+#include <string>
 #include <type_traits>
 #include <utility>
 #include <utils/String16.h>
@@ -21,10 +24,13 @@
 namespace unions {
 class UnionInUnion : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    first = 0,  // android.aidl.tests.unions.EnumUnion first;
-    second,  // int second;
+  enum class Tag : int32_t {
+    first = 0,
+    second = 1,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag first = Tag::first;
+  static const inline Tag second = Tag::second;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, UnionInUnion>;
@@ -113,3 +119,33 @@
 }  // namespace tests
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace tests {
+namespace unions {
+[[nodiscard]] static inline std::string toString(UnionInUnion::Tag val) {
+  switch(val) {
+  case UnionInUnion::Tag::first:
+    return "first";
+  case UnionInUnion::Tag::second:
+    return "second";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace unions
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::tests::unions::UnionInUnion::Tag, 2> enum_values<::android::aidl::tests::unions::UnionInUnion::Tag> = {
+  ::android::aidl::tests::unions::UnionInUnion::Tag::first,
+  ::android::aidl::tests::unions::UnionInUnion::Tag::second,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ArrayOfInterfaces.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ArrayOfInterfaces.java
index 5c86379..ea45e08 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ArrayOfInterfaces.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ArrayOfInterfaces.java
@@ -565,5 +565,11 @@
       this._tag = _tag;
       this._value = _value;
     }
+    public static @interface Tag {
+      public static final int iface = 0;
+      public static final int nullable_iface = 1;
+      public static final int iface_array = 2;
+      public static final int nullable_iface_array = 3;
+    }
   }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/FixedSize.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/FixedSize.java
index 1b4ca32..2ff3068 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/FixedSize.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/FixedSize.java
@@ -404,5 +404,15 @@
       this._tag = _tag;
       this._value = _value;
     }
+    public static @interface Tag {
+      public static final byte booleanValue = 0;
+      public static final byte byteValue = 1;
+      public static final byte charValue = 2;
+      public static final byte intValue = 3;
+      public static final byte longValue = 4;
+      public static final byte floatValue = 5;
+      public static final byte doubleValue = 6;
+      public static final byte enumValue = 7;
+    }
   }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java
index 50d65a6..1530943 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java
@@ -276,6 +276,10 @@
     {
       return null;
     }
+    @Override public int[] GetUnionTags(android.aidl.tests.Union[] input) throws android.os.RemoteException
+    {
+      return null;
+    }
     // Retrieve the ICppJavaTests if the server supports it
     @Override public android.os.IBinder GetCppJavaTests() throws android.os.RemoteException
     {
@@ -573,6 +577,10 @@
     {
       return mImpl.GetNewNameInterface();
     }
+    @Override public int[] GetUnionTags(android.aidl.tests.Union[] input) throws android.os.RemoteException
+    {
+      return mImpl.GetUnionTags(input);
+    }
     // Retrieve the ICppJavaTests if the server supports it
     @Override public android.os.IBinder GetCppJavaTests() throws android.os.RemoteException
     {
@@ -1381,6 +1389,16 @@
           reply.writeStrongInterface(_result);
           break;
         }
+        case TRANSACTION_GetUnionTags:
+        {
+          android.aidl.tests.Union[] _arg0;
+          _arg0 = data.createTypedArray(android.aidl.tests.Union.CREATOR);
+          data.enforceNoDataAvail();
+          int[] _result = this.GetUnionTags(_arg0);
+          reply.writeNoException();
+          reply.writeIntArray(_result);
+          break;
+        }
         case TRANSACTION_GetCppJavaTests:
         {
           android.os.IBinder _result = this.GetCppJavaTests();
@@ -3054,6 +3072,30 @@
         }
         return _result;
       }
+      @Override public int[] GetUnionTags(android.aidl.tests.Union[] input) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+        _data.markSensitive();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        int[] _result;
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeTypedArray(input, 0);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_GetUnionTags, _data, _reply, android.os.IBinder.FLAG_CLEAR_BUF);
+          if (!_status) {
+            if (getDefaultImpl() != null) {
+              return getDefaultImpl().GetUnionTags(input);
+            }
+          }
+          _reply.readException();
+          _result = _reply.createIntArray();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+        return _result;
+      }
       // Retrieve the ICppJavaTests if the server supports it
       @Override public android.os.IBinder GetCppJavaTests() throws android.os.RemoteException
       {
@@ -3166,8 +3208,9 @@
     static final int TRANSACTION_ReverseNullableIBinderArray = (android.os.IBinder.FIRST_CALL_TRANSACTION + 60);
     static final int TRANSACTION_GetOldNameInterface = (android.os.IBinder.FIRST_CALL_TRANSACTION + 61);
     static final int TRANSACTION_GetNewNameInterface = (android.os.IBinder.FIRST_CALL_TRANSACTION + 62);
-    static final int TRANSACTION_GetCppJavaTests = (android.os.IBinder.FIRST_CALL_TRANSACTION + 63);
-    static final int TRANSACTION_getBackendType = (android.os.IBinder.FIRST_CALL_TRANSACTION + 64);
+    static final int TRANSACTION_GetUnionTags = (android.os.IBinder.FIRST_CALL_TRANSACTION + 63);
+    static final int TRANSACTION_GetCppJavaTests = (android.os.IBinder.FIRST_CALL_TRANSACTION + 64);
+    static final int TRANSACTION_getBackendType = (android.os.IBinder.FIRST_CALL_TRANSACTION + 65);
     public static boolean setDefaultImpl(android.aidl.tests.ITestService impl) {
       // Only one user of this interface can use this function
       // at a time. This is a heuristic to detect if two different
@@ -3357,6 +3400,7 @@
   public android.os.IBinder[] ReverseNullableIBinderArray(android.os.IBinder[] input, android.os.IBinder[] repeated) throws android.os.RemoteException;
   public android.aidl.tests.IOldName GetOldNameInterface() throws android.os.RemoteException;
   public android.aidl.tests.INewName GetNewNameInterface() throws android.os.RemoteException;
+  public int[] GetUnionTags(android.aidl.tests.Union[] input) throws android.os.RemoteException;
   // Retrieve the ICppJavaTests if the server supports it
   public android.os.IBinder GetCppJavaTests() throws android.os.RemoteException;
   public byte getBackendType() throws android.os.RemoteException;
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java.d b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java.d
index f82b264..561c508 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java.d
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ITestService.java.d
@@ -9,6 +9,6 @@
   system/tools/aidl/tests/android/aidl/tests/LongEnum.aidl \
   system/tools/aidl/tests/android/aidl/tests/RecursiveList.aidl \
   system/tools/aidl/tests/android/aidl/tests/StructuredParcelable.aidl \
+  system/tools/aidl/tests/android/aidl/tests/Union.aidl \
   system/tools/aidl/tests/android/aidl/tests/extension/ExtendableParcelable.aidl \
-  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl \
-  system/tools/aidl/tests/android/aidl/tests/Union.aidl
+  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ListOfInterfaces.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ListOfInterfaces.java
index fafba0c..6a2564d 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ListOfInterfaces.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/ListOfInterfaces.java
@@ -545,5 +545,11 @@
       this._tag = _tag;
       this._value = _value;
     }
+    public static @interface Tag {
+      public static final int iface = 0;
+      public static final int nullable_iface = 1;
+      public static final int iface_list = 2;
+      public static final int nullable_iface_list = 3;
+    }
   }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/Union.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/Union.java
index 50688a6..018898a 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/Union.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/Union.java
@@ -283,4 +283,13 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int ns = 0;
+    public static final int n = 1;
+    public static final int m = 2;
+    public static final int s = 3;
+    public static final int ibinder = 4;
+    public static final int ss = 5;
+    public static final int be = 6;
+  }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/UnionWithFd.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/UnionWithFd.java
index 941c947..bdd6946 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/UnionWithFd.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/UnionWithFd.java
@@ -137,4 +137,8 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int num = 0;
+    public static final int pfd = 1;
+  }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
index 28df6de..038d813 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/EnumUnion.java
@@ -151,4 +151,8 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int intEnum = 0;
+    public static final int longEnum = 1;
+  }
 }
diff --git a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/UnionInUnion.java b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/UnionInUnion.java
index 241511e..90a68d3 100644
--- a/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/UnionInUnion.java
+++ b/tests/golden_output/aidl-test-interface-java-source/gen/android/aidl/tests/unions/UnionInUnion.java
@@ -161,4 +161,8 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int first = 0;
+    public static final int second = 1;
+  }
 }
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp b/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp
index 0268aaa..8c9ff0a 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp
@@ -1246,7 +1246,25 @@
 
       break;
     }
-    case (FIRST_CALL_TRANSACTION + 63 /*GetCppJavaTests*/): {
+    case (FIRST_CALL_TRANSACTION + 63 /*GetUnionTags*/): {
+      std::vector<::aidl::android::aidl::tests::Union> in_input;
+      std::vector<::aidl::android::aidl::tests::Union::Tag> _aidl_return;
+
+      _aidl_ret_status = ::ndk::AParcel_readData(_aidl_in, &in_input);
+      if (_aidl_ret_status != STATUS_OK) break;
+
+      ::ndk::ScopedAStatus _aidl_status = _aidl_impl->GetUnionTags(in_input, &_aidl_return);
+      _aidl_ret_status = AParcel_writeStatusHeader(_aidl_out, _aidl_status.get());
+      if (_aidl_ret_status != STATUS_OK) break;
+
+      if (!AStatus_isOk(_aidl_status.get())) break;
+
+      _aidl_ret_status = ::ndk::AParcel_writeData(_aidl_out, _aidl_return);
+      if (_aidl_ret_status != STATUS_OK) break;
+
+      break;
+    }
+    case (FIRST_CALL_TRANSACTION + 64 /*GetCppJavaTests*/): {
       ::ndk::SpAIBinder _aidl_return;
 
       ::ndk::ScopedAStatus _aidl_status = _aidl_impl->GetCppJavaTests(&_aidl_return);
@@ -1260,7 +1278,7 @@
 
       break;
     }
-    case (FIRST_CALL_TRANSACTION + 64 /*getBackendType*/): {
+    case (FIRST_CALL_TRANSACTION + 65 /*getBackendType*/): {
       ::aidl::android::aidl::tests::BackendType _aidl_return;
 
       ::ndk::ScopedAStatus _aidl_status = _aidl_impl->getBackendType(&_aidl_return);
@@ -3944,6 +3962,47 @@
   _aidl_status_return:
   return _aidl_status;
 }
+::ndk::ScopedAStatus BpTestService::GetUnionTags(const std::vector<::aidl::android::aidl::tests::Union>& in_input, std::vector<::aidl::android::aidl::tests::Union::Tag>* _aidl_return) {
+  binder_status_t _aidl_ret_status = STATUS_OK;
+  ::ndk::ScopedAStatus _aidl_status;
+  ::ndk::ScopedAParcel _aidl_in;
+  ::ndk::ScopedAParcel _aidl_out;
+
+  _aidl_ret_status = AIBinder_prepareTransaction(asBinder().get(), _aidl_in.getR());
+  AParcel_markSensitive(_aidl_in.get());
+  if (_aidl_ret_status != STATUS_OK) goto _aidl_error;
+
+  _aidl_ret_status = ::ndk::AParcel_writeData(_aidl_in.get(), in_input);
+  if (_aidl_ret_status != STATUS_OK) goto _aidl_error;
+
+  _aidl_ret_status = AIBinder_transact(
+    asBinder().get(),
+    (FIRST_CALL_TRANSACTION + 63 /*GetUnionTags*/),
+    _aidl_in.getR(),
+    _aidl_out.getR(),
+    FLAG_CLEAR_BUF
+    #ifdef BINDER_STABILITY_SUPPORT
+    | FLAG_PRIVATE_LOCAL
+    #endif  // BINDER_STABILITY_SUPPORT
+    );
+  if (_aidl_ret_status == STATUS_UNKNOWN_TRANSACTION && ITestService::getDefaultImpl()) {
+    _aidl_status = ITestService::getDefaultImpl()->GetUnionTags(in_input, _aidl_return);
+    goto _aidl_status_return;
+  }
+  if (_aidl_ret_status != STATUS_OK) goto _aidl_error;
+
+  _aidl_ret_status = AParcel_readStatusHeader(_aidl_out.get(), _aidl_status.getR());
+  if (_aidl_ret_status != STATUS_OK) goto _aidl_error;
+
+  if (!AStatus_isOk(_aidl_status.get())) goto _aidl_status_return;
+  _aidl_ret_status = ::ndk::AParcel_readData(_aidl_out.get(), _aidl_return);
+  if (_aidl_ret_status != STATUS_OK) goto _aidl_error;
+
+  _aidl_error:
+  _aidl_status.set(AStatus_fromStatus(_aidl_ret_status));
+  _aidl_status_return:
+  return _aidl_status;
+}
 ::ndk::ScopedAStatus BpTestService::GetCppJavaTests(::ndk::SpAIBinder* _aidl_return) {
   binder_status_t _aidl_ret_status = STATUS_OK;
   ::ndk::ScopedAStatus _aidl_status;
@@ -3956,7 +4015,7 @@
 
   _aidl_ret_status = AIBinder_transact(
     asBinder().get(),
-    (FIRST_CALL_TRANSACTION + 63 /*GetCppJavaTests*/),
+    (FIRST_CALL_TRANSACTION + 64 /*GetCppJavaTests*/),
     _aidl_in.getR(),
     _aidl_out.getR(),
     FLAG_CLEAR_BUF
@@ -3994,7 +4053,7 @@
 
   _aidl_ret_status = AIBinder_transact(
     asBinder().get(),
-    (FIRST_CALL_TRANSACTION + 64 /*getBackendType*/),
+    (FIRST_CALL_TRANSACTION + 65 /*getBackendType*/),
     _aidl_in.getR(),
     _aidl_out.getR(),
     FLAG_CLEAR_BUF
@@ -4388,6 +4447,11 @@
   _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
   return _aidl_status;
 }
+::ndk::ScopedAStatus ITestServiceDefault::GetUnionTags(const std::vector<::aidl::android::aidl::tests::Union>& /*in_input*/, std::vector<::aidl::android::aidl::tests::Union::Tag>* /*_aidl_return*/) {
+  ::ndk::ScopedAStatus _aidl_status;
+  _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
+  return _aidl_status;
+}
 ::ndk::ScopedAStatus ITestServiceDefault::GetCppJavaTests(::ndk::SpAIBinder* /*_aidl_return*/) {
   ::ndk::ScopedAStatus _aidl_status;
   _aidl_status.set(AStatus_fromStatus(STATUS_UNKNOWN_TRANSACTION));
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp.d b/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp.d
index b46066a..19acd9f 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp.d
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/android/aidl/tests/ITestService.cpp.d
@@ -9,6 +9,6 @@
   system/tools/aidl/tests/android/aidl/tests/LongEnum.aidl \
   system/tools/aidl/tests/android/aidl/tests/RecursiveList.aidl \
   system/tools/aidl/tests/android/aidl/tests/StructuredParcelable.aidl \
+  system/tools/aidl/tests/android/aidl/tests/Union.aidl \
   system/tools/aidl/tests/android/aidl/tests/extension/ExtendableParcelable.aidl \
-  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl \
-  system/tools/aidl/tests/android/aidl/tests/Union.aidl
+  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ArrayOfInterfaces.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ArrayOfInterfaces.h
index 40951a7..b2b9e7b 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ArrayOfInterfaces.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ArrayOfInterfaces.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
@@ -152,13 +154,19 @@
     typedef std::false_type fixed_size;
     static const char* descriptor;
 
-    enum Tag : int32_t {
-      iface = 0,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface iface;
-      nullable_iface,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface nullable_iface;
-      iface_array,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface[] iface_array;
-      nullable_iface_array,  // android.aidl.tests.ArrayOfInterfaces.IEmptyInterface[] nullable_iface_array;
+    enum class Tag : int32_t {
+      iface = 0,
+      nullable_iface = 1,
+      iface_array = 2,
+      nullable_iface_array = 3,
     };
 
+    // Expose tag symbols for legacy code
+    static const inline Tag iface = Tag::iface;
+    static const inline Tag nullable_iface = Tag::nullable_iface;
+    static const inline Tag iface_array = Tag::iface_array;
+    static const inline Tag nullable_iface_array = Tag::nullable_iface_array;
+
     template<typename _Tp>
     static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, MyUnion>;
 
@@ -277,3 +285,39 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(ArrayOfInterfaces::MyUnion::Tag val) {
+  switch(val) {
+  case ArrayOfInterfaces::MyUnion::Tag::iface:
+    return "iface";
+  case ArrayOfInterfaces::MyUnion::Tag::nullable_iface:
+    return "nullable_iface";
+  case ArrayOfInterfaces::MyUnion::Tag::iface_array:
+    return "iface_array";
+  case ArrayOfInterfaces::MyUnion::Tag::nullable_iface_array:
+    return "nullable_iface_array";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag, 4> enum_values<aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag> = {
+  aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::iface,
+  aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::nullable_iface,
+  aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::iface_array,
+  aidl::android::aidl::tests::ArrayOfInterfaces::MyUnion::Tag::nullable_iface_array,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/BpTestService.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/BpTestService.h
index 71b75be..2cd9768 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/BpTestService.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/BpTestService.h
@@ -76,6 +76,7 @@
   ::ndk::ScopedAStatus ReverseNullableIBinderArray(const std::optional<std::vector<::ndk::SpAIBinder>>& in_input, std::optional<std::vector<::ndk::SpAIBinder>>* out_repeated, std::optional<std::vector<::ndk::SpAIBinder>>* _aidl_return) override;
   ::ndk::ScopedAStatus GetOldNameInterface(std::shared_ptr<::aidl::android::aidl::tests::IOldName>* _aidl_return) override;
   ::ndk::ScopedAStatus GetNewNameInterface(std::shared_ptr<::aidl::android::aidl::tests::INewName>* _aidl_return) override;
+  ::ndk::ScopedAStatus GetUnionTags(const std::vector<::aidl::android::aidl::tests::Union>& in_input, std::vector<::aidl::android::aidl::tests::Union::Tag>* _aidl_return) override;
   ::ndk::ScopedAStatus GetCppJavaTests(::ndk::SpAIBinder* _aidl_return) override;
   ::ndk::ScopedAStatus getBackendType(::aidl::android::aidl::tests::BackendType* _aidl_return) override;
 };
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/FixedSize.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/FixedSize.h
index 7c92e11..541757a 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/FixedSize.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/FixedSize.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -10,6 +11,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -37,17 +39,27 @@
     typedef std::true_type fixed_size;
     static const char* descriptor;
 
-    enum Tag : uint8_t {
-      booleanValue = 0,  // boolean booleanValue;
-      byteValue,  // byte byteValue;
-      charValue,  // char charValue;
-      intValue,  // int intValue;
-      longValue,  // long longValue;
-      floatValue,  // float floatValue;
-      doubleValue,  // double doubleValue;
-      enumValue,  // android.aidl.tests.LongEnum enumValue;
+    enum class Tag : int8_t {
+      booleanValue = 0,
+      byteValue = 1,
+      charValue = 2,
+      intValue = 3,
+      longValue = 4,
+      floatValue = 5,
+      doubleValue = 6,
+      enumValue = 7,
     };
 
+    // Expose tag symbols for legacy code
+    static const inline Tag booleanValue = Tag::booleanValue;
+    static const inline Tag byteValue = Tag::byteValue;
+    static const inline Tag charValue = Tag::charValue;
+    static const inline Tag intValue = Tag::intValue;
+    static const inline Tag longValue = Tag::longValue;
+    static const inline Tag floatValue = Tag::floatValue;
+    static const inline Tag doubleValue = Tag::doubleValue;
+    static const inline Tag enumValue = Tag::enumValue;
+
     template <Tag _Tag>
     using _at = typename std::tuple_element<static_cast<size_t>(_Tag), std::tuple<bool, int8_t, char16_t, int32_t, int64_t, float, double, ::aidl::android::aidl::tests::LongEnum>>::type;
     template <Tag _Tag, typename _Type>
@@ -130,7 +142,7 @@
       return os.str();
     }
   private:
-    Tag _tag __attribute__((aligned (1))) = booleanValue;
+    Tag _tag = booleanValue;
     union _value_t {
       _value_t() {}
       ~_value_t() {}
@@ -233,3 +245,51 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(FixedSize::FixedUnion::Tag val) {
+  switch(val) {
+  case FixedSize::FixedUnion::Tag::booleanValue:
+    return "booleanValue";
+  case FixedSize::FixedUnion::Tag::byteValue:
+    return "byteValue";
+  case FixedSize::FixedUnion::Tag::charValue:
+    return "charValue";
+  case FixedSize::FixedUnion::Tag::intValue:
+    return "intValue";
+  case FixedSize::FixedUnion::Tag::longValue:
+    return "longValue";
+  case FixedSize::FixedUnion::Tag::floatValue:
+    return "floatValue";
+  case FixedSize::FixedUnion::Tag::doubleValue:
+    return "doubleValue";
+  case FixedSize::FixedUnion::Tag::enumValue:
+    return "enumValue";
+  default:
+    return std::to_string(static_cast<int8_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::FixedSize::FixedUnion::Tag, 8> enum_values<aidl::android::aidl::tests::FixedSize::FixedUnion::Tag> = {
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::booleanValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::byteValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::charValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::intValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::longValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::floatValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::doubleValue,
+  aidl::android::aidl::tests::FixedSize::FixedUnion::Tag::enumValue,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ITestService.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ITestService.h
index dfa1692..2c396b8 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ITestService.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ITestService.h
@@ -20,6 +20,7 @@
 #include <aidl/android/aidl/tests/LongEnum.h>
 #include <aidl/android/aidl/tests/RecursiveList.h>
 #include <aidl/android/aidl/tests/StructuredParcelable.h>
+#include <aidl/android/aidl/tests/Union.h>
 #include <aidl/android/aidl/tests/extension/ExtendableParcelable.h>
 #ifdef BINDER_STABILITY_SUPPORT
 #include <android/binder_stability.h>
@@ -280,8 +281,9 @@
   static constexpr uint32_t TRANSACTION_ReverseNullableIBinderArray = FIRST_CALL_TRANSACTION + 60;
   static constexpr uint32_t TRANSACTION_GetOldNameInterface = FIRST_CALL_TRANSACTION + 61;
   static constexpr uint32_t TRANSACTION_GetNewNameInterface = FIRST_CALL_TRANSACTION + 62;
-  static constexpr uint32_t TRANSACTION_GetCppJavaTests = FIRST_CALL_TRANSACTION + 63;
-  static constexpr uint32_t TRANSACTION_getBackendType = FIRST_CALL_TRANSACTION + 64;
+  static constexpr uint32_t TRANSACTION_GetUnionTags = FIRST_CALL_TRANSACTION + 63;
+  static constexpr uint32_t TRANSACTION_GetCppJavaTests = FIRST_CALL_TRANSACTION + 64;
+  static constexpr uint32_t TRANSACTION_getBackendType = FIRST_CALL_TRANSACTION + 65;
 
   static std::shared_ptr<ITestService> fromBinder(const ::ndk::SpAIBinder& binder);
   static binder_status_t writeToParcel(AParcel* parcel, const std::shared_ptr<ITestService>& instance);
@@ -351,6 +353,7 @@
   virtual ::ndk::ScopedAStatus ReverseNullableIBinderArray(const std::optional<std::vector<::ndk::SpAIBinder>>& in_input, std::optional<std::vector<::ndk::SpAIBinder>>* out_repeated, std::optional<std::vector<::ndk::SpAIBinder>>* _aidl_return) = 0;
   virtual ::ndk::ScopedAStatus GetOldNameInterface(std::shared_ptr<::aidl::android::aidl::tests::IOldName>* _aidl_return) = 0;
   virtual ::ndk::ScopedAStatus GetNewNameInterface(std::shared_ptr<::aidl::android::aidl::tests::INewName>* _aidl_return) = 0;
+  virtual ::ndk::ScopedAStatus GetUnionTags(const std::vector<::aidl::android::aidl::tests::Union>& in_input, std::vector<::aidl::android::aidl::tests::Union::Tag>* _aidl_return) = 0;
   virtual ::ndk::ScopedAStatus GetCppJavaTests(::ndk::SpAIBinder* _aidl_return) = 0;
   virtual ::ndk::ScopedAStatus getBackendType(::aidl::android::aidl::tests::BackendType* _aidl_return) = 0;
 private:
@@ -421,6 +424,7 @@
   ::ndk::ScopedAStatus ReverseNullableIBinderArray(const std::optional<std::vector<::ndk::SpAIBinder>>& in_input, std::optional<std::vector<::ndk::SpAIBinder>>* out_repeated, std::optional<std::vector<::ndk::SpAIBinder>>* _aidl_return) override;
   ::ndk::ScopedAStatus GetOldNameInterface(std::shared_ptr<::aidl::android::aidl::tests::IOldName>* _aidl_return) override;
   ::ndk::ScopedAStatus GetNewNameInterface(std::shared_ptr<::aidl::android::aidl::tests::INewName>* _aidl_return) override;
+  ::ndk::ScopedAStatus GetUnionTags(const std::vector<::aidl::android::aidl::tests::Union>& in_input, std::vector<::aidl::android::aidl::tests::Union::Tag>* _aidl_return) override;
   ::ndk::ScopedAStatus GetCppJavaTests(::ndk::SpAIBinder* _aidl_return) override;
   ::ndk::ScopedAStatus getBackendType(::aidl::android::aidl::tests::BackendType* _aidl_return) override;
   ::ndk::SpAIBinder asBinder() override;
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ListOfInterfaces.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ListOfInterfaces.h
index 7c93f75..e0dc968 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ListOfInterfaces.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/ListOfInterfaces.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
@@ -152,13 +154,19 @@
     typedef std::false_type fixed_size;
     static const char* descriptor;
 
-    enum Tag : int32_t {
-      iface = 0,  // android.aidl.tests.ListOfInterfaces.IEmptyInterface iface;
-      nullable_iface,  // android.aidl.tests.ListOfInterfaces.IEmptyInterface nullable_iface;
-      iface_list,  // List<android.aidl.tests.ListOfInterfaces.IEmptyInterface> iface_list;
-      nullable_iface_list,  // List<android.aidl.tests.ListOfInterfaces.IEmptyInterface> nullable_iface_list;
+    enum class Tag : int32_t {
+      iface = 0,
+      nullable_iface = 1,
+      iface_list = 2,
+      nullable_iface_list = 3,
     };
 
+    // Expose tag symbols for legacy code
+    static const inline Tag iface = Tag::iface;
+    static const inline Tag nullable_iface = Tag::nullable_iface;
+    static const inline Tag iface_list = Tag::iface_list;
+    static const inline Tag nullable_iface_list = Tag::nullable_iface_list;
+
     template<typename _Tp>
     static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, MyUnion>;
 
@@ -277,3 +285,39 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(ListOfInterfaces::MyUnion::Tag val) {
+  switch(val) {
+  case ListOfInterfaces::MyUnion::Tag::iface:
+    return "iface";
+  case ListOfInterfaces::MyUnion::Tag::nullable_iface:
+    return "nullable_iface";
+  case ListOfInterfaces::MyUnion::Tag::iface_list:
+    return "iface_list";
+  case ListOfInterfaces::MyUnion::Tag::nullable_iface_list:
+    return "nullable_iface_list";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag, 4> enum_values<aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag> = {
+  aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::iface,
+  aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::nullable_iface,
+  aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::iface_list,
+  aidl::android::aidl::tests::ListOfInterfaces::MyUnion::Tag::nullable_iface_list,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/Union.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/Union.h
index 27ee97c..a94d03f 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/Union.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/Union.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -30,16 +32,25 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    ns = 0,  // int[] ns;
-    n,  // int n;
-    m,  // int m;
-    s,  // String s;
-    ibinder,  // IBinder ibinder;
-    ss,  // List<String> ss;
-    be,  // android.aidl.tests.ByteEnum be;
+  enum class Tag : int32_t {
+    ns = 0,
+    n = 1,
+    m = 2,
+    s = 3,
+    ibinder = 4,
+    ss = 5,
+    be = 6,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag ns = Tag::ns;
+  static const inline Tag n = Tag::n;
+  static const inline Tag m = Tag::m;
+  static const inline Tag s = Tag::s;
+  static const inline Tag ibinder = Tag::ibinder;
+  static const inline Tag ss = Tag::ss;
+  static const inline Tag be = Tag::be;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, Union>;
 
@@ -131,3 +142,48 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(Union::Tag val) {
+  switch(val) {
+  case Union::Tag::ns:
+    return "ns";
+  case Union::Tag::n:
+    return "n";
+  case Union::Tag::m:
+    return "m";
+  case Union::Tag::s:
+    return "s";
+  case Union::Tag::ibinder:
+    return "ibinder";
+  case Union::Tag::ss:
+    return "ss";
+  case Union::Tag::be:
+    return "be";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::Union::Tag, 7> enum_values<aidl::android::aidl::tests::Union::Tag> = {
+  aidl::android::aidl::tests::Union::Tag::ns,
+  aidl::android::aidl::tests::Union::Tag::n,
+  aidl::android::aidl::tests::Union::Tag::m,
+  aidl::android::aidl::tests::Union::Tag::s,
+  aidl::android::aidl::tests::Union::Tag::ibinder,
+  aidl::android::aidl::tests::Union::Tag::ss,
+  aidl::android::aidl::tests::Union::Tag::be,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/UnionWithFd.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/UnionWithFd.h
index 818ff01..986b542 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/UnionWithFd.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/UnionWithFd.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -29,11 +31,15 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    num = 0,  // int num;
-    pfd,  // ParcelFileDescriptor pfd;
+  enum class Tag : int32_t {
+    num = 0,
+    pfd = 1,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag num = Tag::num;
+  static const inline Tag pfd = Tag::pfd;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, UnionWithFd>;
 
@@ -119,3 +125,33 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+[[nodiscard]] static inline std::string toString(UnionWithFd::Tag val) {
+  switch(val) {
+  case UnionWithFd::Tag::num:
+    return "num";
+  case UnionWithFd::Tag::pfd:
+    return "pfd";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::UnionWithFd::Tag, 2> enum_values<aidl::android::aidl::tests::UnionWithFd::Tag> = {
+  aidl::android::aidl::tests::UnionWithFd::Tag::num,
+  aidl::android::aidl::tests::UnionWithFd::Tag::pfd,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/EnumUnion.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/EnumUnion.h
index dc3c3fb..f52a45e 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/EnumUnion.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/EnumUnion.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -32,11 +34,15 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    intEnum = 0,  // android.aidl.tests.IntEnum intEnum;
-    longEnum,  // android.aidl.tests.LongEnum longEnum;
+  enum class Tag : int32_t {
+    intEnum = 0,
+    longEnum = 1,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag intEnum = Tag::intEnum;
+  static const inline Tag longEnum = Tag::longEnum;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, EnumUnion>;
 
@@ -123,3 +129,35 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+namespace unions {
+[[nodiscard]] static inline std::string toString(EnumUnion::Tag val) {
+  switch(val) {
+  case EnumUnion::Tag::intEnum:
+    return "intEnum";
+  case EnumUnion::Tag::longEnum:
+    return "longEnum";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace unions
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::unions::EnumUnion::Tag, 2> enum_values<aidl::android::aidl::tests::unions::EnumUnion::Tag> = {
+  aidl::android::aidl::tests::unions::EnumUnion::Tag::intEnum,
+  aidl::android::aidl::tests::unions::EnumUnion::Tag::longEnum,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/UnionInUnion.h b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/UnionInUnion.h
index 147a566..a41dfbb 100644
--- a/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/UnionInUnion.h
+++ b/tests/golden_output/aidl-test-interface-ndk-source/gen/include/aidl/android/aidl/tests/unions/UnionInUnion.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -31,11 +33,15 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    first = 0,  // android.aidl.tests.unions.EnumUnion first;
-    second,  // int second;
+  enum class Tag : int32_t {
+    first = 0,
+    second = 1,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag first = Tag::first;
+  static const inline Tag second = Tag::second;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, UnionInUnion>;
 
@@ -122,3 +128,35 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace tests {
+namespace unions {
+[[nodiscard]] static inline std::string toString(UnionInUnion::Tag val) {
+  switch(val) {
+  case UnionInUnion::Tag::first:
+    return "first";
+  case UnionInUnion::Tag::second:
+    return "second";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace unions
+}  // namespace tests
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::tests::unions::UnionInUnion::Tag, 2> enum_values<aidl::android::aidl::tests::unions::UnionInUnion::Tag> = {
+  aidl::android::aidl::tests::unions::UnionInUnion::Tag::first,
+  aidl::android::aidl::tests::unions::UnionInUnion::Tag::second,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ArrayOfInterfaces.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ArrayOfInterfaces.rs
index ee4b1f1..42b05dc 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ArrayOfInterfaces.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ArrayOfInterfaces.rs
@@ -384,6 +384,18 @@
   impl binder::binder_impl::ParcelableMetadata for MyUnion {
     fn get_descriptor() -> &'static str { "android.aidl.tests.ArrayOfInterfaces.MyUnion" }
   }
+  pub mod Tag {
+    #![allow(non_upper_case_globals)]
+    use binder::declare_binder_enum;
+    declare_binder_enum! {
+      Tag : [i32; 4] {
+        iface = 0,
+        nullable_iface = 1,
+        iface_array = 2,
+        nullable_iface_array = 3,
+      }
+    }
+  }
 }
 pub(crate) mod mangled {
  pub use super::ArrayOfInterfaces as _7_android_4_aidl_5_tests_17_ArrayOfInterfaces;
@@ -391,4 +403,5 @@
  pub use super::IMyInterface::IMyInterface as _7_android_4_aidl_5_tests_17_ArrayOfInterfaces_12_IMyInterface;
  pub use super::MyParcelable::MyParcelable as _7_android_4_aidl_5_tests_17_ArrayOfInterfaces_12_MyParcelable;
  pub use super::MyUnion::MyUnion as _7_android_4_aidl_5_tests_17_ArrayOfInterfaces_7_MyUnion;
+ pub use super::MyUnion::Tag::Tag as _7_android_4_aidl_5_tests_17_ArrayOfInterfaces_7_MyUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/FixedSize.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/FixedSize.rs
index ef73f72..2ea4430 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/FixedSize.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/FixedSize.rs
@@ -216,9 +216,26 @@
   impl binder::binder_impl::ParcelableMetadata for FixedUnion {
     fn get_descriptor() -> &'static str { "android.aidl.tests.FixedSize.FixedUnion" }
   }
+  pub mod Tag {
+    #![allow(non_upper_case_globals)]
+    use binder::declare_binder_enum;
+    declare_binder_enum! {
+      Tag : [i8; 8] {
+        booleanValue = 0,
+        byteValue = 1,
+        charValue = 2,
+        intValue = 3,
+        longValue = 4,
+        floatValue = 5,
+        doubleValue = 6,
+        enumValue = 7,
+      }
+    }
+  }
 }
 pub(crate) mod mangled {
  pub use super::FixedSize as _7_android_4_aidl_5_tests_9_FixedSize;
  pub use super::FixedParcelable::FixedParcelable as _7_android_4_aidl_5_tests_9_FixedSize_15_FixedParcelable;
  pub use super::FixedUnion::FixedUnion as _7_android_4_aidl_5_tests_9_FixedSize_10_FixedUnion;
+ pub use super::FixedUnion::Tag::Tag as _7_android_4_aidl_5_tests_9_FixedSize_10_FixedUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs
index 9e8fd92..36c03d4 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs
@@ -78,6 +78,7 @@
   fn ReverseNullableIBinderArray(&self, _arg_input: Option<&[Option<binder::SpIBinder>]>, _arg_repeated: &mut Option<Vec<Option<binder::SpIBinder>>>) -> binder::Result<Option<Vec<Option<binder::SpIBinder>>>>;
   fn GetOldNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_IOldName>>;
   fn GetNewNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>>;
+  fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>>;
   fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>>;
   fn getBackendType(&self) -> binder::Result<crate::mangled::_7_android_4_aidl_5_tests_11_BackendType>;
   fn getDefaultImpl() -> ITestServiceDefaultRef where Self: Sized {
@@ -153,6 +154,7 @@
   fn ReverseNullableIBinderArray<'a>(&'a self, _arg_input: Option<&'a [Option<binder::SpIBinder>]>, _arg_repeated: &'a mut Option<Vec<Option<binder::SpIBinder>>>) -> binder::BoxFuture<'a, binder::Result<Option<Vec<Option<binder::SpIBinder>>>>>;
   fn GetOldNameInterface<'a>(&'a self) -> binder::BoxFuture<'a, binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_IOldName>>>;
   fn GetNewNameInterface<'a>(&'a self) -> binder::BoxFuture<'a, binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>>>;
+  fn GetUnionTags<'a>(&'a self, _arg_input: &'a [crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::BoxFuture<'a, binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>>>;
   fn GetCppJavaTests<'a>(&'a self) -> binder::BoxFuture<'a, binder::Result<Option<binder::SpIBinder>>>;
   fn getBackendType<'a>(&'a self) -> binder::BoxFuture<'a, binder::Result<crate::mangled::_7_android_4_aidl_5_tests_11_BackendType>>;
 }
@@ -223,6 +225,7 @@
   async fn ReverseNullableIBinderArray(&self, _arg_input: Option<&[Option<binder::SpIBinder>]>, _arg_repeated: &mut Option<Vec<Option<binder::SpIBinder>>>) -> binder::Result<Option<Vec<Option<binder::SpIBinder>>>>;
   async fn GetOldNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_IOldName>>;
   async fn GetNewNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>>;
+  async fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>>;
   async fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>>;
   async fn getBackendType(&self) -> binder::Result<crate::mangled::_7_android_4_aidl_5_tests_11_BackendType>;
 }
@@ -435,6 +438,9 @@
       fn GetNewNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>> {
         self._rt.block_on(self._inner.GetNewNameInterface())
       }
+      fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>> {
+        self._rt.block_on(self._inner.GetUnionTags(_arg_input))
+      }
       fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>> {
         self._rt.block_on(self._inner.GetCppJavaTests())
       }
@@ -636,6 +642,9 @@
   fn GetNewNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>> {
     Err(binder::StatusCode::UNKNOWN_TRANSACTION.into())
   }
+  fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>> {
+    Err(binder::StatusCode::UNKNOWN_TRANSACTION.into())
+  }
   fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>> {
     Err(binder::StatusCode::UNKNOWN_TRANSACTION.into())
   }
@@ -707,8 +716,9 @@
   pub const ReverseNullableIBinderArray: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 60;
   pub const GetOldNameInterface: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 61;
   pub const GetNewNameInterface: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 62;
-  pub const GetCppJavaTests: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 63;
-  pub const getBackendType: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 64;
+  pub const GetUnionTags: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 63;
+  pub const GetCppJavaTests: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 64;
+  pub const getBackendType: binder::binder_impl::TransactionCode = binder::binder_impl::FIRST_CALL_TRANSACTION + 65;
 }
 pub type ITestServiceDefaultRef = Option<std::sync::Arc<dyn ITestServiceDefault>>;
 use lazy_static::lazy_static;
@@ -1949,6 +1959,24 @@
     let _aidl_return: binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName> = _aidl_reply.read()?;
     Ok(_aidl_return)
   }
+  fn build_parcel_GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<binder::binder_impl::Parcel> {
+    let mut aidl_data = self.binder.prepare_transact()?;
+    aidl_data.mark_sensitive();
+    aidl_data.write(_arg_input)?;
+    Ok(aidl_data)
+  }
+  fn read_response_GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union], _aidl_reply: std::result::Result<binder::binder_impl::Parcel, binder::StatusCode>) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>> {
+    if let Err(binder::StatusCode::UNKNOWN_TRANSACTION) = _aidl_reply {
+      if let Some(_aidl_default_impl) = <Self as ITestService>::getDefaultImpl() {
+        return _aidl_default_impl.GetUnionTags(_arg_input);
+      }
+    }
+    let _aidl_reply = _aidl_reply?;
+    let _aidl_status: binder::Status = _aidl_reply.read()?;
+    if !_aidl_status.is_ok() { return Err(_aidl_status); }
+    let _aidl_return: Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag> = _aidl_reply.read()?;
+    Ok(_aidl_return)
+  }
   fn build_parcel_GetCppJavaTests(&self) -> binder::Result<binder::binder_impl::Parcel> {
     let mut aidl_data = self.binder.prepare_transact()?;
     aidl_data.mark_sensitive();
@@ -2300,6 +2328,11 @@
     let _aidl_reply = self.binder.submit_transact(transactions::GetNewNameInterface, _aidl_data, binder::binder_impl::FLAG_CLEAR_BUF | binder::binder_impl::FLAG_PRIVATE_LOCAL);
     self.read_response_GetNewNameInterface(_aidl_reply)
   }
+  fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>> {
+    let _aidl_data = self.build_parcel_GetUnionTags(_arg_input)?;
+    let _aidl_reply = self.binder.submit_transact(transactions::GetUnionTags, _aidl_data, binder::binder_impl::FLAG_CLEAR_BUF | binder::binder_impl::FLAG_PRIVATE_LOCAL);
+    self.read_response_GetUnionTags(_arg_input, _aidl_reply)
+  }
   fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>> {
     let _aidl_data = self.build_parcel_GetCppJavaTests()?;
     let _aidl_reply = self.binder.submit_transact(transactions::GetCppJavaTests, _aidl_data, binder::binder_impl::FLAG_CLEAR_BUF | binder::binder_impl::FLAG_PRIVATE_LOCAL);
@@ -3126,6 +3159,19 @@
       }
     )
   }
+  fn GetUnionTags<'a>(&'a self, _arg_input: &'a [crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::BoxFuture<'a, binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>>> {
+    let _aidl_data = match self.build_parcel_GetUnionTags(_arg_input) {
+      Ok(_aidl_data) => _aidl_data,
+      Err(err) => return Box::pin(std::future::ready(Err(err))),
+    };
+    let binder = self.binder.clone();
+    P::spawn(
+      move || binder.submit_transact(transactions::GetUnionTags, _aidl_data, binder::binder_impl::FLAG_CLEAR_BUF | binder::binder_impl::FLAG_PRIVATE_LOCAL),
+      move |_aidl_reply| async move {
+        self.read_response_GetUnionTags(_arg_input, _aidl_reply)
+      }
+    )
+  }
   fn GetCppJavaTests<'a>(&'a self) -> binder::BoxFuture<'a, binder::Result<Option<binder::SpIBinder>>> {
     let _aidl_data = match self.build_parcel_GetCppJavaTests() {
       Ok(_aidl_data) => _aidl_data,
@@ -3217,6 +3263,7 @@
   fn ReverseNullableIBinderArray(&self, _arg_input: Option<&[Option<binder::SpIBinder>]>, _arg_repeated: &mut Option<Vec<Option<binder::SpIBinder>>>) -> binder::Result<Option<Vec<Option<binder::SpIBinder>>>> { self.0.ReverseNullableIBinderArray(_arg_input, _arg_repeated) }
   fn GetOldNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_IOldName>> { self.0.GetOldNameInterface() }
   fn GetNewNameInterface(&self) -> binder::Result<binder::Strong<dyn crate::mangled::_7_android_4_aidl_5_tests_8_INewName>> { self.0.GetNewNameInterface() }
+  fn GetUnionTags(&self, _arg_input: &[crate::mangled::_7_android_4_aidl_5_tests_5_Union]) -> binder::Result<Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union_3_Tag>> { self.0.GetUnionTags(_arg_input) }
   fn GetCppJavaTests(&self) -> binder::Result<Option<binder::SpIBinder>> { self.0.GetCppJavaTests() }
   fn getBackendType(&self) -> binder::Result<crate::mangled::_7_android_4_aidl_5_tests_11_BackendType> { self.0.getBackendType() }
 }
@@ -4019,6 +4066,18 @@
       }
       Ok(())
     }
+    transactions::GetUnionTags => {
+      let _arg_input: Vec<crate::mangled::_7_android_4_aidl_5_tests_5_Union> = _aidl_data.read()?;
+      let _aidl_return = _aidl_service.GetUnionTags(&_arg_input);
+      match &_aidl_return {
+        Ok(_aidl_return) => {
+          _aidl_reply.write(&binder::Status::from(binder::StatusCode::OK))?;
+          _aidl_reply.write(_aidl_return)?;
+        }
+        Err(_aidl_status) => _aidl_reply.write(_aidl_status)?
+      }
+      Ok(())
+    }
     transactions::GetCppJavaTests => {
       let _aidl_return = _aidl_service.GetCppJavaTests();
       match &_aidl_return {
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs.d b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs.d
index 3f2111f..9786dc1 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs.d
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ITestService.rs.d
@@ -9,6 +9,6 @@
   system/tools/aidl/tests/android/aidl/tests/LongEnum.aidl \
   system/tools/aidl/tests/android/aidl/tests/RecursiveList.aidl \
   system/tools/aidl/tests/android/aidl/tests/StructuredParcelable.aidl \
+  system/tools/aidl/tests/android/aidl/tests/Union.aidl \
   system/tools/aidl/tests/android/aidl/tests/extension/ExtendableParcelable.aidl \
-  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl \
-  system/tools/aidl/tests/android/aidl/tests/Union.aidl
+  system/tools/aidl/tests/android/aidl/tests/ConstantExpressionEnum.aidl
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ListOfInterfaces.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ListOfInterfaces.rs
index 274006e..4f5d5c6 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ListOfInterfaces.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/ListOfInterfaces.rs
@@ -380,6 +380,18 @@
   impl binder::binder_impl::ParcelableMetadata for MyUnion {
     fn get_descriptor() -> &'static str { "android.aidl.tests.ListOfInterfaces.MyUnion" }
   }
+  pub mod Tag {
+    #![allow(non_upper_case_globals)]
+    use binder::declare_binder_enum;
+    declare_binder_enum! {
+      Tag : [i32; 4] {
+        iface = 0,
+        nullable_iface = 1,
+        iface_list = 2,
+        nullable_iface_list = 3,
+      }
+    }
+  }
 }
 pub(crate) mod mangled {
  pub use super::ListOfInterfaces as _7_android_4_aidl_5_tests_16_ListOfInterfaces;
@@ -387,4 +399,5 @@
  pub use super::IMyInterface::IMyInterface as _7_android_4_aidl_5_tests_16_ListOfInterfaces_12_IMyInterface;
  pub use super::MyParcelable::MyParcelable as _7_android_4_aidl_5_tests_16_ListOfInterfaces_12_MyParcelable;
  pub use super::MyUnion::MyUnion as _7_android_4_aidl_5_tests_16_ListOfInterfaces_7_MyUnion;
+ pub use super::MyUnion::Tag::Tag as _7_android_4_aidl_5_tests_16_ListOfInterfaces_7_MyUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/Union.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/Union.rs
index 44f1f78..a2f9295 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/Union.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/Union.rs
@@ -98,6 +98,22 @@
 impl binder::binder_impl::ParcelableMetadata for Union {
   fn get_descriptor() -> &'static str { "android.aidl.tests.Union" }
 }
+pub mod Tag {
+  #![allow(non_upper_case_globals)]
+  use binder::declare_binder_enum;
+  declare_binder_enum! {
+    Tag : [i32; 7] {
+      ns = 0,
+      n = 1,
+      m = 2,
+      s = 3,
+      ibinder = 4,
+      ss = 5,
+      be = 6,
+    }
+  }
+}
 pub(crate) mod mangled {
  pub use super::Union as _7_android_4_aidl_5_tests_5_Union;
+ pub use super::Tag::Tag as _7_android_4_aidl_5_tests_5_Union_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/UnionWithFd.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/UnionWithFd.rs
index 29af5a3..49f9f16 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/UnionWithFd.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/UnionWithFd.rs
@@ -48,6 +48,17 @@
 impl binder::binder_impl::ParcelableMetadata for UnionWithFd {
   fn get_descriptor() -> &'static str { "android.aidl.tests.UnionWithFd" }
 }
+pub mod Tag {
+  #![allow(non_upper_case_globals)]
+  use binder::declare_binder_enum;
+  declare_binder_enum! {
+    Tag : [i32; 2] {
+      num = 0,
+      pfd = 1,
+    }
+  }
+}
 pub(crate) mod mangled {
  pub use super::UnionWithFd as _7_android_4_aidl_5_tests_11_UnionWithFd;
+ pub use super::Tag::Tag as _7_android_4_aidl_5_tests_11_UnionWithFd_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/EnumUnion.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/EnumUnion.rs
index bf142f7..4d5fe95 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/EnumUnion.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/EnumUnion.rs
@@ -47,6 +47,17 @@
 impl binder::binder_impl::ParcelableMetadata for EnumUnion {
   fn get_descriptor() -> &'static str { "android.aidl.tests.unions.EnumUnion" }
 }
+pub mod Tag {
+  #![allow(non_upper_case_globals)]
+  use binder::declare_binder_enum;
+  declare_binder_enum! {
+    Tag : [i32; 2] {
+      intEnum = 0,
+      longEnum = 1,
+    }
+  }
+}
 pub(crate) mod mangled {
  pub use super::EnumUnion as _7_android_4_aidl_5_tests_6_unions_9_EnumUnion;
+ pub use super::Tag::Tag as _7_android_4_aidl_5_tests_6_unions_9_EnumUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/UnionInUnion.rs b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/UnionInUnion.rs
index fd2d68d..511f44c 100644
--- a/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/UnionInUnion.rs
+++ b/tests/golden_output/aidl-test-interface-rust-source/gen/android/aidl/tests/unions/UnionInUnion.rs
@@ -47,6 +47,17 @@
 impl binder::binder_impl::ParcelableMetadata for UnionInUnion {
   fn get_descriptor() -> &'static str { "android.aidl.tests.unions.UnionInUnion" }
 }
+pub mod Tag {
+  #![allow(non_upper_case_globals)]
+  use binder::declare_binder_enum;
+  declare_binder_enum! {
+    Tag : [i32; 2] {
+      first = 0,
+      second = 1,
+    }
+  }
+}
 pub(crate) mod mangled {
  pub use super::UnionInUnion as _7_android_4_aidl_5_tests_6_unions_12_UnionInUnion;
+ pub use super::Tag::Tag as _7_android_4_aidl_5_tests_6_unions_12_UnionInUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl-test-versioned-interface-V1-cpp-source/gen/include/android/aidl/versioned/tests/BazUnion.h b/tests/golden_output/aidl-test-versioned-interface-V1-cpp-source/gen/include/android/aidl/versioned/tests/BazUnion.h
index c8ba05b..25e0d16 100644
--- a/tests/golden_output/aidl-test-versioned-interface-V1-cpp-source/gen/include/android/aidl/versioned/tests/BazUnion.h
+++ b/tests/golden_output/aidl-test-versioned-interface-V1-cpp-source/gen/include/android/aidl/versioned/tests/BazUnion.h
@@ -1,10 +1,13 @@
 #pragma once
 
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
 #include <cstdint>
+#include <string>
 #include <type_traits>
 #include <utility>
 #include <utils/String16.h>
@@ -20,9 +23,11 @@
 namespace tests {
 class BazUnion : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    intNum = 0,  // int intNum;
+  enum class Tag : int32_t {
+    intNum = 0,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag intNum = Tag::intNum;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, BazUnion>;
@@ -110,3 +115,30 @@
 }  // namespace versioned
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace versioned {
+namespace tests {
+[[nodiscard]] static inline std::string toString(BazUnion::Tag val) {
+  switch(val) {
+  case BazUnion::Tag::intNum:
+    return "intNum";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace versioned
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::versioned::tests::BazUnion::Tag, 1> enum_values<::android::aidl::versioned::tests::BazUnion::Tag> = {
+  ::android::aidl::versioned::tests::BazUnion::Tag::intNum,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl-test-versioned-interface-V1-java-source/gen/android/aidl/versioned/tests/BazUnion.java b/tests/golden_output/aidl-test-versioned-interface-V1-java-source/gen/android/aidl/versioned/tests/BazUnion.java
index 29da9c2..3f0e3d7 100644
--- a/tests/golden_output/aidl-test-versioned-interface-V1-java-source/gen/android/aidl/versioned/tests/BazUnion.java
+++ b/tests/golden_output/aidl-test-versioned-interface-V1-java-source/gen/android/aidl/versioned/tests/BazUnion.java
@@ -102,4 +102,7 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int intNum = 0;
+  }
 }
diff --git a/tests/golden_output/aidl-test-versioned-interface-V1-ndk-source/gen/include/aidl/android/aidl/versioned/tests/BazUnion.h b/tests/golden_output/aidl-test-versioned-interface-V1-ndk-source/gen/include/aidl/android/aidl/versioned/tests/BazUnion.h
index 24a8014..9218a0b 100644
--- a/tests/golden_output/aidl-test-versioned-interface-V1-ndk-source/gen/include/aidl/android/aidl/versioned/tests/BazUnion.h
+++ b/tests/golden_output/aidl-test-versioned-interface-V1-ndk-source/gen/include/aidl/android/aidl/versioned/tests/BazUnion.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -30,10 +32,13 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    intNum = 0,  // int intNum;
+  enum class Tag : int32_t {
+    intNum = 0,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag intNum = Tag::intNum;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, BazUnion>;
 
@@ -119,3 +124,32 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace versioned {
+namespace tests {
+[[nodiscard]] static inline std::string toString(BazUnion::Tag val) {
+  switch(val) {
+  case BazUnion::Tag::intNum:
+    return "intNum";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace tests
+}  // namespace versioned
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::versioned::tests::BazUnion::Tag, 1> enum_values<aidl::android::aidl::versioned::tests::BazUnion::Tag> = {
+  aidl::android::aidl::versioned::tests::BazUnion::Tag::intNum,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/golden_output/aidl-test-versioned-interface-V1-rust-source/gen/android/aidl/versioned/tests/BazUnion.rs b/tests/golden_output/aidl-test-versioned-interface-V1-rust-source/gen/android/aidl/versioned/tests/BazUnion.rs
index 5e30930..b60ff88 100644
--- a/tests/golden_output/aidl-test-versioned-interface-V1-rust-source/gen/android/aidl/versioned/tests/BazUnion.rs
+++ b/tests/golden_output/aidl-test-versioned-interface-V1-rust-source/gen/android/aidl/versioned/tests/BazUnion.rs
@@ -37,6 +37,16 @@
 impl binder::binder_impl::ParcelableMetadata for BazUnion {
   fn get_descriptor() -> &'static str { "android.aidl.versioned.tests.BazUnion" }
 }
+pub mod Tag {
+  #![allow(non_upper_case_globals)]
+  use binder::declare_binder_enum;
+  declare_binder_enum! {
+    Tag : [i32; 1] {
+      intNum = 0,
+    }
+  }
+}
 pub(crate) mod mangled {
  pub use super::BazUnion as _7_android_4_aidl_9_versioned_5_tests_8_BazUnion;
+ pub use super::Tag::Tag as _7_android_4_aidl_9_versioned_5_tests_8_BazUnion_3_Tag;
 }
diff --git a/tests/golden_output/aidl_test_loggable_interface-cpp-source/gen/include/android/aidl/loggable/Union.h b/tests/golden_output/aidl_test_loggable_interface-cpp-source/gen/include/android/aidl/loggable/Union.h
index fbaf018..9b67e56 100644
--- a/tests/golden_output/aidl_test_loggable_interface-cpp-source/gen/include/android/aidl/loggable/Union.h
+++ b/tests/golden_output/aidl_test_loggable_interface-cpp-source/gen/include/android/aidl/loggable/Union.h
@@ -1,6 +1,8 @@
 #pragma once
 
 #include <android/binder_to_string.h>
+#include <array>
+#include <binder/Enums.h>
 #include <binder/Parcel.h>
 #include <binder/Status.h>
 #include <cassert>
@@ -20,10 +22,13 @@
 namespace loggable {
 class Union : public ::android::Parcelable {
 public:
-  enum Tag : int32_t {
-    num = 0,  // int num;
-    str,  // String str;
+  enum class Tag : int32_t {
+    num = 0,
+    str = 1,
   };
+  // Expose tag symbols for legacy code
+  static const inline Tag num = Tag::num;
+  static const inline Tag str = Tag::str;
 
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, Union>;
@@ -111,3 +116,31 @@
 }  // namespace loggable
 }  // namespace aidl
 }  // namespace android
+namespace android {
+namespace aidl {
+namespace loggable {
+[[nodiscard]] static inline std::string toString(Union::Tag val) {
+  switch(val) {
+  case Union::Tag::num:
+    return "num";
+  case Union::Tag::str:
+    return "str";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace loggable
+}  // namespace aidl
+}  // namespace android
+namespace android {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<::android::aidl::loggable::Union::Tag, 2> enum_values<::android::aidl::loggable::Union::Tag> = {
+  ::android::aidl::loggable::Union::Tag::num,
+  ::android::aidl::loggable::Union::Tag::str,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace android
diff --git a/tests/golden_output/aidl_test_loggable_interface-java-source/gen/android/aidl/loggable/Union.java b/tests/golden_output/aidl_test_loggable_interface-java-source/gen/android/aidl/loggable/Union.java
index 1751db3..f85770f 100644
--- a/tests/golden_output/aidl_test_loggable_interface-java-source/gen/android/aidl/loggable/Union.java
+++ b/tests/golden_output/aidl_test_loggable_interface-java-source/gen/android/aidl/loggable/Union.java
@@ -127,4 +127,8 @@
     this._tag = _tag;
     this._value = _value;
   }
+  public static @interface Tag {
+    public static final int num = 0;
+    public static final int str = 1;
+  }
 }
diff --git a/tests/golden_output/aidl_test_loggable_interface-ndk-source/gen/include/aidl/android/aidl/loggable/Union.h b/tests/golden_output/aidl_test_loggable_interface-ndk-source/gen/include/aidl/android/aidl/loggable/Union.h
index 2969134..ef0532f 100644
--- a/tests/golden_output/aidl_test_loggable_interface-ndk-source/gen/include/aidl/android/aidl/loggable/Union.h
+++ b/tests/golden_output/aidl_test_loggable_interface-ndk-source/gen/include/aidl/android/aidl/loggable/Union.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <memory>
@@ -9,6 +10,7 @@
 #include <utility>
 #include <variant>
 #include <vector>
+#include <android/binder_enums.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #include <android/binder_to_string.h>
@@ -29,11 +31,15 @@
   typedef std::false_type fixed_size;
   static const char* descriptor;
 
-  enum Tag : int32_t {
-    num = 0,  // int num;
-    str,  // String str;
+  enum class Tag : int32_t {
+    num = 0,
+    str = 1,
   };
 
+  // Expose tag symbols for legacy code
+  static const inline Tag num = Tag::num;
+  static const inline Tag str = Tag::str;
+
   template<typename _Tp>
   static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, Union>;
 
@@ -119,3 +125,33 @@
 }  // namespace aidl
 }  // namespace android
 }  // namespace aidl
+namespace aidl {
+namespace android {
+namespace aidl {
+namespace loggable {
+[[nodiscard]] static inline std::string toString(Union::Tag val) {
+  switch(val) {
+  case Union::Tag::num:
+    return "num";
+  case Union::Tag::str:
+    return "str";
+  default:
+    return std::to_string(static_cast<int32_t>(val));
+  }
+}
+}  // namespace loggable
+}  // namespace aidl
+}  // namespace android
+}  // namespace aidl
+namespace ndk {
+namespace internal {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++17-extensions"
+template <>
+constexpr inline std::array<aidl::android::aidl::loggable::Union::Tag, 2> enum_values<aidl::android::aidl::loggable::Union::Tag> = {
+  aidl::android::aidl::loggable::Union::Tag::num,
+  aidl::android::aidl::loggable::Union::Tag::str,
+};
+#pragma clang diagnostic pop
+}  // namespace internal
+}  // namespace ndk
diff --git a/tests/java/src/android/aidl/service/TestServiceServer.java b/tests/java/src/android/aidl/service/TestServiceServer.java
index ad98416..51a7520 100644
--- a/tests/java/src/android/aidl/service/TestServiceServer.java
+++ b/tests/java/src/android/aidl/service/TestServiceServer.java
@@ -592,6 +592,15 @@
     return new MyNewName();
   }
 
+  @Override
+  public int[] GetUnionTags(Union[] input) throws RemoteException {
+    int[] tags = new int[input.length];
+    for (int i = 0; i < input.length; i++) {
+      tags[i] = input[i].getTag();
+    }
+    return tags;
+  }
+
   class MyCppJavaTests extends ICppJavaTests.Stub {
     @Override
     public BadParcelable RepeatBadParcelable(BadParcelable input) throws RemoteException {
diff --git a/tests/java/src/android/aidl/tests/TestServiceClient.java b/tests/java/src/android/aidl/tests/TestServiceClient.java
index e9140cc..6dfacc9 100644
--- a/tests/java/src/android/aidl/tests/TestServiceClient.java
+++ b/tests/java/src/android/aidl/tests/TestServiceClient.java
@@ -956,6 +956,13 @@
     }
 
     @Test
+    public void testGetUnionTags() throws RemoteException {
+      assertArrayEquals(new int[] {}, service.GetUnionTags(new Union[] {}));
+      assertArrayEquals(new int[] {Union.n, Union.ns},
+          service.GetUnionTags(new Union[] {Union.n(0), Union.ns(new int[] {})}));
+    }
+
+    @Test
     public void testDescribeContents() throws Exception {
       CompilerChecks cc = new CompilerChecks();
       cc.pfd_array = new ParcelFileDescriptor[] {null, null, null};
diff --git a/tests/rust/test_client.rs b/tests/rust/test_client.rs
index 35c3af3..81315c9 100644
--- a/tests/rust/test_client.rs
+++ b/tests/rust/test_client.rs
@@ -822,6 +822,15 @@
 }
 
 #[test]
+fn test_get_union_tags() {
+    let service = get_test_service();
+    let result = service.GetUnionTags(&[]);
+    assert_eq!(result, Ok(vec![]));
+    let result = service.GetUnionTags(&[Union::Union::N(0), Union::Union::Ns(vec![])]);
+    assert_eq!(result, Ok(vec![Union::Tag::Tag::n, Union::Tag::Tag::ns]));
+}
+
+#[test]
 fn test_unions() {
     assert_eq!(Union::Union::default(), Union::Union::Ns(vec![]));
     assert_eq!(EnumUnion::default(), EnumUnion::IntEnum(IntEnum::FOO));
diff --git a/tests/rust/test_service.rs b/tests/rust/test_service.rs
index 1d3c092..3a5c09c 100644
--- a/tests/rust/test_service.rs
+++ b/tests/rust/test_service.rs
@@ -441,6 +441,21 @@
         Ok(INewName::BnNewName::new_binder(NewName, BinderFeatures::default()))
     }
 
+    fn GetUnionTags(&self, input: &[Union::Union]) -> binder::Result<Vec<Union::Tag::Tag>> {
+        Ok(input
+            .iter()
+            .map(|u| match u {
+                Union::Union::Ns(_) => Union::Tag::Tag::ns,
+                Union::Union::N(_) => Union::Tag::Tag::n,
+                Union::Union::M(_) => Union::Tag::Tag::m,
+                Union::Union::S(_) => Union::Tag::Tag::s,
+                Union::Union::Ibinder(_) => Union::Tag::Tag::ibinder,
+                Union::Union::Ss(_) => Union::Tag::Tag::ss,
+                Union::Union::Be(_) => Union::Tag::Tag::be,
+            })
+            .collect::<Vec<_>>())
+    }
+
     fn GetCppJavaTests(&self) -> binder::Result<Option<SpIBinder>> {
         Ok(None)
     }
diff --git a/tests/rust/test_service_async.rs b/tests/rust/test_service_async.rs
index f6cd039..cfc0f28 100644
--- a/tests/rust/test_service_async.rs
+++ b/tests/rust/test_service_async.rs
@@ -483,6 +483,21 @@
         Ok(INewName::BnNewName::new_async_binder(NewName, rt(), BinderFeatures::default()))
     }
 
+    async fn GetUnionTags(&self, input: &[Union::Union]) -> binder::Result<Vec<Union::Tag::Tag>> {
+        Ok(input
+            .iter()
+            .map(|u| match u {
+                Union::Union::Ns(_) => Union::Tag::Tag::ns,
+                Union::Union::N(_) => Union::Tag::Tag::n,
+                Union::Union::M(_) => Union::Tag::Tag::m,
+                Union::Union::S(_) => Union::Tag::Tag::s,
+                Union::Union::Ibinder(_) => Union::Tag::Tag::ibinder,
+                Union::Union::Ss(_) => Union::Tag::Tag::ss,
+                Union::Union::Be(_) => Union::Tag::Tag::be,
+            })
+            .collect::<Vec<_>>())
+    }
+
     async fn GetCppJavaTests(&self) -> binder::Result<Option<SpIBinder>> {
         Ok(None)
     }