Add union support (Rust)

`union` in AIDL is represented as `enum` in Rust.

Bug: 170689477
Test: aidl_unittests / aidl_integration_test
Change-Id: I5fad878a32e24f84854f69d4da91737e84cadb63
diff --git a/Android.bp b/Android.bp
index 90fe3ff..4c3d839 100644
--- a/Android.bp
+++ b/Android.bp
@@ -257,6 +257,8 @@
         "tests/android/aidl/tests/OtherParcelableForToString.aidl",
         "tests/android/aidl/tests/ParcelableForToString.aidl",
         "tests/android/aidl/tests/StructuredParcelable.aidl",
+        "tests/android/aidl/tests/Union.aidl",
+        "tests/android/aidl/tests/UnionWithFd.aidl",
     ],
     path: "tests",
 }
@@ -266,8 +268,6 @@
     srcs: [
         "tests/android/aidl/tests/ICppJavaTests.aidl",
         "tests/android/aidl/tests/SimpleParcelable.aidl",
-        "tests/android/aidl/tests/Union.aidl",
-        "tests/android/aidl/tests/UnionWithFd.aidl",
         "tests/android/aidl/tests/extension/*.aidl",
     ],
     path: "tests",
diff --git a/aidl_language.cpp b/aidl_language.cpp
index bf6d2d9..728eb7f 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -1180,9 +1180,9 @@
       AidlWithFields(variables) {}
 
 std::set<AidlAnnotation::Type> AidlUnionDecl::GetSupportedAnnotations() const {
-  return {AidlAnnotation::Type::VINTF_STABILITY, AidlAnnotation::Type::HIDE,
-          AidlAnnotation::Type::JAVA_PASSTHROUGH, AidlAnnotation::Type::JAVA_DERIVE,
-          AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE};
+  return {AidlAnnotation::Type::VINTF_STABILITY,     AidlAnnotation::Type::HIDE,
+          AidlAnnotation::Type::JAVA_PASSTHROUGH,    AidlAnnotation::Type::JAVA_DERIVE,
+          AidlAnnotation::Type::JAVA_ONLY_IMMUTABLE, AidlAnnotation::Type::RUST_DERIVE};
 }
 
 void AidlUnionDecl::Dump(CodeWriter* writer) const {
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index a8136f4..1bcde1c 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -3424,6 +3424,83 @@
 }
 )";
 
+const char kUnionExampleExpectedOutputRust[] = R"(#[derive(Debug)]
+pub enum Foo {
+  Ns(Vec<i32>),
+  E(crate::mangled::_1_a_8_ByteEnum),
+  Pfd(Option<binder::parcel::ParcelFileDescriptor>),
+}
+pub(crate) mod mangled { pub use super::Foo as _1_a_3_Foo; }
+impl Default for Foo {
+  fn default() -> Self {
+    Self::Ns(vec!{42})
+  }
+}
+impl binder::parcel::Serialize for Foo {
+  fn serialize(&self, parcel: &mut binder::parcel::Parcel) -> binder::Result<()> {
+    <Self as binder::parcel::SerializeOption>::serialize_option(Some(self), parcel)
+  }
+}
+impl binder::parcel::SerializeArray for Foo {}
+impl binder::parcel::SerializeOption for Foo {
+  fn serialize_option(this: Option<&Self>, parcel: &mut binder::parcel::Parcel) -> binder::Result<()> {
+    let this = if let Some(this) = this {
+      parcel.write(&1i32)?;
+      this
+    } else {
+      return parcel.write(&0i32);
+    };
+    match this {
+      Self::Ns(v) => {
+        parcel.write(&0i32)?;
+        parcel.write(v)
+      }
+      Self::E(v) => {
+        parcel.write(&1i32)?;
+        parcel.write(v)
+      }
+      Self::Pfd(v) => {
+        parcel.write(&2i32)?;
+        let __field_ref = v.as_ref().ok_or(binder::StatusCode::UNEXPECTED_NULL)?;
+        parcel.write(__field_ref)
+      }
+    }
+  }
+}
+impl binder::parcel::Deserialize for Foo {
+  fn deserialize(parcel: &binder::parcel::Parcel) -> binder::Result<Self> {
+    <Self as binder::parcel::DeserializeOption>::deserialize_option(parcel)
+       .transpose()
+       .unwrap_or(Err(binder::StatusCode::UNEXPECTED_NULL))
+  }
+}
+impl binder::parcel::DeserializeArray for Foo {}
+impl binder::parcel::DeserializeOption for Foo {
+  fn deserialize_option(parcel: &binder::parcel::Parcel) -> binder::Result<Option<Self>> {
+    let status: i32 = parcel.read()?;
+    if status == 0 { return Ok(None); }
+    let tag: i32 = parcel.read()?;
+    match tag {
+      0 => {
+        let value: Vec<i32> = parcel.read()?;
+        Ok(Some(Self::Ns(value)))
+      }
+      1 => {
+        let value: crate::mangled::_1_a_8_ByteEnum = parcel.read()?;
+        Ok(Some(Self::E(value)))
+      }
+      2 => {
+        let value: Option<binder::parcel::ParcelFileDescriptor> = Some(parcel.read()?);
+        Ok(Some(Self::Pfd(value)))
+      }
+      _ => {
+        Err(binder::StatusCode::BAD_VALUE)
+      }
+    }
+  }
+}
+)";
+
 struct AidlUnionTest : ::testing::Test {
   void SetUp() override {
     io_delegate_.SetFileContents("a/Foo.aidl", R"(
@@ -3483,7 +3560,11 @@
   Compile("java");
   EXPECT_COMPILE_OUTPUTS(
       map<string, string>({{"out/a/Foo.java", kUnionExampleExpectedOutputJava}}));
-  // TODO(b/170689477) Rust
+}
+
+TEST_F(AidlUnionTest, Example_Rust) {
+  Compile("rust");
+  EXPECT_COMPILE_OUTPUTS(map<string, string>({{"out/a/Foo.rs", kUnionExampleExpectedOutputRust}}));
 }
 
 TEST_P(AidlTest, UnionRejectsEmptyDecl) {
diff --git a/generate_rust.cpp b/generate_rust.cpp
index d0800f5..e768820 100644
--- a/generate_rust.cpp
+++ b/generate_rust.cpp
@@ -459,6 +459,18 @@
   return true;
 }
 
+void GenerateParcelBody(CodeWriter& out, const AidlStructuredParcelable* parcel,
+                        const AidlTypenames& typenames) {
+  out << "pub struct " << parcel->GetName() << " {\n";
+  out.Indent();
+  for (const auto& variable : parcel->GetFields()) {
+    auto field_type = RustNameOf(variable->GetType(), typenames, StorageMode::PARCELABLE_FIELD);
+    out << "pub " << variable->GetName() << ": " << field_type << ",\n";
+  }
+  out.Dedent();
+  out << "}\n";
+}
+
 void GenerateParcelDefault(CodeWriter& out, const AidlStructuredParcelable* parcel) {
   out << "impl Default for " << parcel->GetName() << " {\n";
   out.Indent();
@@ -481,7 +493,140 @@
   out << "}\n";
 }
 
-void GenerateParcelSerialize(CodeWriter& out, const AidlStructuredParcelable* parcel,
+void GenerateParcelSerializeBody(CodeWriter& out, const AidlStructuredParcelable* parcel,
+                                 const AidlTypenames& typenames) {
+  out << "parcel.sized_write(|subparcel| {\n";
+  out.Indent();
+  for (const auto& variable : parcel->GetFields()) {
+    if (!TypeHasDefault(variable->GetType(), typenames)) {
+      out << "let __field_ref = this." << variable->GetName()
+          << ".as_ref().ok_or(binder::StatusCode::UNEXPECTED_NULL)?;\n";
+      out << "subparcel.write(__field_ref)?;\n";
+    } else {
+      out << "subparcel.write(&this." << variable->GetName() << ")?;\n";
+    }
+  }
+  out << "Ok(())\n";
+  out.Dedent();
+  out << "})\n";
+}
+
+void GenerateParcelDeserializeBody(CodeWriter& out, const AidlStructuredParcelable* parcel,
+                                   const AidlTypenames& typenames) {
+  out << "let start_pos = parcel.get_data_position();\n";
+  out << "let parcelable_size: i32 = parcel.read()?;\n";
+  out << "if parcelable_size < 0 { return Err(binder::StatusCode::BAD_VALUE); }\n";
+
+  // Pre-emit the common field epilogue code, shared between all fields:
+  ostringstream epilogue;
+  epilogue << "if (parcel.get_data_position() - start_pos) == parcelable_size {\n";
+  // We assume the lhs can never be > parcelable_size, because then the read
+  // immediately preceding this check would have returned NOT_ENOUGH_DATA
+  epilogue << "  return Ok(Some(result));\n";
+  epilogue << "}\n";
+  string epilogue_str = epilogue.str();
+
+  out << "let mut result = Self::default();\n";
+  for (const auto& variable : parcel->GetFields()) {
+    if (!TypeHasDefault(variable->GetType(), typenames)) {
+      out << "result." << variable->GetName() << " = Some(parcel.read()?);\n";
+    } else {
+      out << "result." << variable->GetName() << " = parcel.read()?;\n";
+    }
+    out << epilogue_str;
+  }
+
+  out << "Ok(Some(result))\n";
+}
+
+void GenerateParcelBody(CodeWriter& out, const AidlUnionDecl* parcel,
+                        const AidlTypenames& typenames) {
+  out << "pub enum " << parcel->GetName() << " {\n";
+  out.Indent();
+  for (const auto& variable : parcel->GetFields()) {
+    auto field_type = RustNameOf(variable->GetType(), typenames, StorageMode::PARCELABLE_FIELD);
+    out << variable->GetCapitalizedName() << "(" << field_type << "),\n";
+  }
+  out.Dedent();
+  out << "}\n";
+}
+
+void GenerateParcelDefault(CodeWriter& out, const AidlUnionDecl* parcel) {
+  out << "impl Default for " << parcel->GetName() << " {\n";
+  out.Indent();
+  out << "fn default() -> Self {\n";
+  out.Indent();
+
+  AIDL_FATAL_IF(parcel->GetFields().empty(), *parcel)
+      << "Union '" << parcel->GetName() << "' is empty.";
+  const auto& first_field = parcel->GetFields()[0];
+  const auto& first_value = first_field->ValueString(ConstantValueDecorator);
+
+  out << "Self::";
+  if (first_field->GetDefaultValue()) {
+    out << first_field->GetCapitalizedName() << "(" << first_value << ")\n";
+  } else {
+    out << first_field->GetCapitalizedName() << "(Default::default())\n";
+  }
+
+  out.Dedent();
+  out << "}\n";
+  out.Dedent();
+  out << "}\n";
+}
+
+void GenerateParcelSerializeBody(CodeWriter& out, const AidlUnionDecl* parcel,
+                                 const AidlTypenames& typenames) {
+  out << "match this {\n";
+  out.Indent();
+  int tag = 0;
+  for (const auto& variable : parcel->GetFields()) {
+    out << "Self::" << variable->GetCapitalizedName() << "(v) => {\n";
+    out.Indent();
+    out << "parcel.write(&" << std::to_string(tag++) << "i32)?;\n";
+    if (!TypeHasDefault(variable->GetType(), typenames)) {
+      out << "let __field_ref = v.as_ref().ok_or(binder::StatusCode::UNEXPECTED_NULL)?;\n";
+      out << "parcel.write(__field_ref)\n";
+    } else {
+      out << "parcel.write(v)\n";
+    }
+    out.Dedent();
+    out << "}\n";
+  }
+  out.Dedent();
+  out << "}\n";
+}
+
+void GenerateParcelDeserializeBody(CodeWriter& out, const AidlUnionDecl* parcel,
+                                   const AidlTypenames& typenames) {
+  out << "let tag: i32 = parcel.read()?;\n";
+  out << "match tag {\n";
+  out.Indent();
+  int tag = 0;
+  for (const auto& variable : parcel->GetFields()) {
+    auto field_type = RustNameOf(variable->GetType(), typenames, StorageMode::PARCELABLE_FIELD);
+
+    out << std::to_string(tag++) << " => {\n";
+    out.Indent();
+    out << "let value: " << field_type << " = ";
+    if (!TypeHasDefault(variable->GetType(), typenames)) {
+      out << "Some(parcel.read()?);\n";
+    } else {
+      out << "parcel.read()?;\n";
+    }
+    out << "Ok(Some(Self::" << variable->GetCapitalizedName() << "(value)))\n";
+    out.Dedent();
+    out << "}\n";
+  }
+  out << "_ => {\n";
+  out << "  Err(binder::StatusCode::BAD_VALUE)\n";
+  out << "}\n";
+  out.Dedent();
+  out << "}\n";
+}
+
+template <typename ParcelableType>
+void GenerateParcelSerialize(CodeWriter& out, const ParcelableType* parcel,
                              const AidlTypenames& typenames) {
   out << "impl binder::parcel::Serialize for " << parcel->GetName() << " {\n";
   out << "  fn serialize(&self, parcel: &mut binder::parcel::Parcel) -> binder::Result<()> {\n";
@@ -502,27 +647,17 @@
   out << "} else {\n";
   out << "  return parcel.write(&0i32);\n";
   out << "};\n";
-  out << "parcel.sized_write(|subparcel| {\n";
-  out.Indent();
-  for (const auto& variable : parcel->GetFields()) {
-    if (!TypeHasDefault(variable->GetType(), typenames)) {
-      out << "let __field_ref = this." << variable->GetName()
-          << ".as_ref().ok_or(binder::StatusCode::UNEXPECTED_NULL)?;\n";
-      out << "subparcel.write(__field_ref)?;\n";
-    } else {
-      out << "subparcel.write(&this." << variable->GetName() << ")?;\n";
-    }
-  }
-  out << "Ok(())\n";
-  out.Dedent();
-  out << "})\n";
+
+  GenerateParcelSerializeBody(out, parcel, typenames);
+
   out.Dedent();
   out << "}\n";
   out.Dedent();
   out << "}\n";
 }
 
-void GenerateParcelDeserialize(CodeWriter& out, const AidlStructuredParcelable* parcel,
+template <typename ParcelableType>
+void GenerateParcelDeserialize(CodeWriter& out, const ParcelableType* parcel,
                                const AidlTypenames& typenames) {
   out << "impl binder::parcel::Deserialize for " << parcel->GetName() << " {\n";
   out << "  fn deserialize(parcel: &binder::parcel::Parcel) -> binder::Result<Self> {\n";
@@ -535,39 +670,23 @@
   out << "impl binder::parcel::DeserializeArray for " << parcel->GetName() << " {}\n";
 
   out << "impl binder::parcel::DeserializeOption for " << parcel->GetName() << " {\n";
-  out << "  fn deserialize_option(parcel: &binder::parcel::Parcel) -> binder::Result<Option<Self>> "
+  out.Indent();
+  out << "fn deserialize_option(parcel: &binder::parcel::Parcel) -> binder::Result<Option<Self>> "
          "{\n";
-  out << "    let status: i32 = parcel.read()?;\n";
-  out << "    if status == 0 { return Ok(None); }\n";
-  out << "    let start_pos = parcel.get_data_position();\n";
-  out << "    let parcelable_size: i32 = parcel.read()?;\n";
-  out << "    if parcelable_size < 0 { return Err(binder::StatusCode::BAD_VALUE); }\n";
+  out.Indent();
+  out << "let status: i32 = parcel.read()?;\n";
+  out << "if status == 0 { return Ok(None); }\n";
 
-  // Pre-emit the common field epilogue code, shared between all fields:
-  ostringstream epilogue;
-  epilogue << "    if (parcel.get_data_position() - start_pos) == parcelable_size {\n";
-  // We assume the lhs can never be > parcelable_size, because then the read
-  // immediately preceding this check would have returned NOT_ENOUGH_DATA
-  epilogue << "      return Ok(Some(result));\n";
-  epilogue << "    }\n";
-  string epilogue_str = epilogue.str();
+  GenerateParcelDeserializeBody(out, parcel, typenames);
 
-  out << "    let mut result = Self::default();\n";
-  for (const auto& variable : parcel->GetFields()) {
-    if (!TypeHasDefault(variable->GetType(), typenames)) {
-      out << "    result." << variable->GetName() << " = Some(parcel.read()?);\n";
-    } else {
-      out << "    result." << variable->GetName() << " = parcel.read()?;\n";
-    }
-    out << epilogue_str;
-  }
-
-  out << "    Ok(Some(result))\n";
-  out << "  }\n";
+  out.Dedent();
+  out << "}\n";
+  out.Dedent();
   out << "}\n";
 }
 
-bool GenerateRustParcel(const string& filename, const AidlStructuredParcelable* parcel,
+template <typename ParcelableType>
+bool GenerateRustParcel(const string& filename, const ParcelableType* parcel,
                         const AidlTypenames& typenames, const IoDelegate& io_delegate) {
   CodeWriterPtr code_writer = io_delegate.GetCodeWriter(filename);
 
@@ -585,20 +704,11 @@
   }
 
   *code_writer << "#[derive(" << Join(derives, ", ") << ")]\n";
-  *code_writer << "pub struct " << parcel->GetName() << " {\n";
-  code_writer->Indent();
-  for (const auto& variable : parcel->GetFields()) {
-    auto field_type = RustNameOf(variable->GetType(), typenames, StorageMode::PARCELABLE_FIELD);
-    *code_writer << "pub " << variable->GetName() << ": " << field_type << ",\n";
-  }
-  code_writer->Dedent();
-  *code_writer << "}\n";
-
+  GenerateParcelBody(*code_writer, parcel, typenames);
   GenerateMangledAlias(*code_writer, parcel);
   GenerateParcelDefault(*code_writer, parcel);
   GenerateParcelSerialize(*code_writer, parcel, typenames);
   GenerateParcelDeserialize(*code_writer, parcel, typenames);
-
   return true;
 }
 
@@ -634,6 +744,10 @@
     return GenerateRustParcel(filename, parcelable, typenames, io_delegate);
   }
 
+  if (const AidlUnionDecl* parcelable = defined_type->AsUnionDeclaration(); parcelable != nullptr) {
+    return GenerateRustParcel(filename, parcelable, typenames, io_delegate);
+  }
+
   if (const AidlEnumDeclaration* enum_decl = defined_type->AsEnumDeclaration();
       enum_decl != nullptr) {
     return GenerateRustEnumDeclaration(filename, enum_decl, typenames, io_delegate);
diff --git a/tests/aidl_test_client_parcelables.cpp b/tests/aidl_test_client_parcelables.cpp
index f4ab2f6..696261e 100644
--- a/tests/aidl_test_client_parcelables.cpp
+++ b/tests/aidl_test_client_parcelables.cpp
@@ -297,6 +297,8 @@
 
   EXPECT_EQ(parcelable.addString1, "hello world!");
   EXPECT_EQ(parcelable.addString2, "The quick brown fox jumps over the lazy dog.");
+
+  EXPECT_EQ(parcelable.u->get<Union::ns>(), vector<int32_t>({1, 2, 3}));
 }
 
 TEST_F(AidlTest, EmptyParcelableHolder) {
diff --git a/tests/aidl_test_service.cpp b/tests/aidl_test_service.cpp
index fc0eccd..cb2409f 100644
--- a/tests/aidl_test_service.cpp
+++ b/tests/aidl_test_service.cpp
@@ -574,6 +574,7 @@
     parcelable->const_exprs_9 = ConstantExpressionEnum::hexInt32_3;
     parcelable->const_exprs_10 = ConstantExpressionEnum::hexInt64_1;
 
+    parcelable->u = Union::make<Union::ns>({1, 2, 3});
     return Status::ok();
   }
 
diff --git a/tests/android/aidl/tests/StructuredParcelable.aidl b/tests/android/aidl/tests/StructuredParcelable.aidl
index a50bb15..9809de3 100644
--- a/tests/android/aidl/tests/StructuredParcelable.aidl
+++ b/tests/android/aidl/tests/StructuredParcelable.aidl
@@ -20,6 +20,7 @@
 import android.aidl.tests.IntEnum;
 import android.aidl.tests.LongEnum;
 import android.aidl.tests.ConstantExpressionEnum;
+import android.aidl.tests.Union;
 
 @JavaDerive(toString=true)
 @RustDerive(Clone=true, PartialEq=true)
@@ -163,5 +164,7 @@
     // String expressions
     @utf8InCpp String addString1 = "hello" + " world!";
     @utf8InCpp String addString2 = "The quick brown fox jumps " + "over the lazy dog.";
+
+    @nullable Union u;
 }
 
diff --git a/tests/android/aidl/tests/Union.aidl b/tests/android/aidl/tests/Union.aidl
index f42ef16..a65d9c8 100644
--- a/tests/android/aidl/tests/Union.aidl
+++ b/tests/android/aidl/tests/Union.aidl
@@ -17,6 +17,8 @@
 package android.aidl.tests;
 import android.aidl.tests.ByteEnum;
 
+@JavaDerive(toString=true)
+@RustDerive(Clone=true, PartialEq=true)
 union Union {
     int[] ns;
     int n;
diff --git a/tests/java/src/android/aidl/tests/TestServiceClient.java b/tests/java/src/android/aidl/tests/TestServiceClient.java
index fd0c0d6..de9f25e 100644
--- a/tests/java/src/android/aidl/tests/TestServiceClient.java
+++ b/tests/java/src/android/aidl/tests/TestServiceClient.java
@@ -609,59 +609,62 @@
         assertThat(p.const_exprs_9, is(1));
         assertThat(p.const_exprs_10, is(1));
 
-        final String expected = "android.aidl.tests.StructuredParcelable{" +
-            "shouldContainThreeFs: [17, 17, 17], " +
-            "f: 17, " +
-            "shouldBeJerry: Jerry, " +
-            "shouldBeByteBar: 2, " +
-            "shouldBeIntBar: 2000, " +
-            "shouldBeLongBar: 200000000000, " +
-            "shouldContainTwoByteFoos: [1, 1], " +
-            "shouldContainTwoIntFoos: [1000, 1000], " +
-            "shouldContainTwoLongFoos: [100000000000, 100000000000], " +
-            "stringDefaultsToFoo: foo, " +
-            "byteDefaultsToFour: 4, " +
-            "intDefaultsToFive: 5, " +
-            "longDefaultsToNegativeSeven: -7, " +
-            "booleanDefaultsToTrue: true, " +
-            "charDefaultsToC: C, " +
-            "floatDefaultsToPi: 3.14, " +
-            "doubleWithDefault: -3.14E17, " +
-            "arrayDefaultsTo123: [1, 2, 3], " +
-            "arrayDefaultsToEmpty: [], " +
-            "boolDefault: false, " +
-            "byteDefault: 0, " +
-            "intDefault: 0, " +
-            "longDefault: 0, " +
-            "floatDefault: 0.0, " +
-            "doubleDefault: 0.0, " +
-            "checkDoubleFromFloat: 3.14, " +
-            "checkStringArray1: [a, b], " +
-            "checkStringArray2: [a, b], " +
-            "int32_min: -2147483648, " +
-            "int32_max: 2147483647, " +
-            "int64_max: 9223372036854775807, " +
-            "hexInt32_neg_1: -1, " +
-            "ibinder: null, " +
-            "int32_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
-            "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
-            "1, 1, 1, 1], " +
-            "int64_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], " +
-            "hexInt32_pos_1: 1, " +
-            "hexInt64_pos_1: 1, " +
-            "const_exprs_1: 1, " +
-            "const_exprs_2: 1, " +
-            "const_exprs_3: 1, " +
-            "const_exprs_4: 1, " +
-            "const_exprs_5: 1, " +
-            "const_exprs_6: 1, " +
-            "const_exprs_7: 1, " +
-            "const_exprs_8: 1, " +
-            "const_exprs_9: 1, " +
-            "const_exprs_10: 1, " +
-            "addString1: hello world!, " +
-            "addString2: The quick brown fox jumps over the lazy dog." +
-            "}";
+        assertThat(p.u.getNs(), is(new int[] {1, 2, 3}));
+
+        final String expected = "android.aidl.tests.StructuredParcelable{"
+            + "shouldContainThreeFs: [17, 17, 17], "
+            + "f: 17, "
+            + "shouldBeJerry: Jerry, "
+            + "shouldBeByteBar: 2, "
+            + "shouldBeIntBar: 2000, "
+            + "shouldBeLongBar: 200000000000, "
+            + "shouldContainTwoByteFoos: [1, 1], "
+            + "shouldContainTwoIntFoos: [1000, 1000], "
+            + "shouldContainTwoLongFoos: [100000000000, 100000000000], "
+            + "stringDefaultsToFoo: foo, "
+            + "byteDefaultsToFour: 4, "
+            + "intDefaultsToFive: 5, "
+            + "longDefaultsToNegativeSeven: -7, "
+            + "booleanDefaultsToTrue: true, "
+            + "charDefaultsToC: C, "
+            + "floatDefaultsToPi: 3.14, "
+            + "doubleWithDefault: -3.14E17, "
+            + "arrayDefaultsTo123: [1, 2, 3], "
+            + "arrayDefaultsToEmpty: [], "
+            + "boolDefault: false, "
+            + "byteDefault: 0, "
+            + "intDefault: 0, "
+            + "longDefault: 0, "
+            + "floatDefault: 0.0, "
+            + "doubleDefault: 0.0, "
+            + "checkDoubleFromFloat: 3.14, "
+            + "checkStringArray1: [a, b], "
+            + "checkStringArray2: [a, b], "
+            + "int32_min: -2147483648, "
+            + "int32_max: 2147483647, "
+            + "int64_max: 9223372036854775807, "
+            + "hexInt32_neg_1: -1, "
+            + "ibinder: null, "
+            + "int32_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
+            + "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, "
+            + "1, 1, 1, 1], "
+            + "int64_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "
+            + "hexInt32_pos_1: 1, "
+            + "hexInt64_pos_1: 1, "
+            + "const_exprs_1: 1, "
+            + "const_exprs_2: 1, "
+            + "const_exprs_3: 1, "
+            + "const_exprs_4: 1, "
+            + "const_exprs_5: 1, "
+            + "const_exprs_6: 1, "
+            + "const_exprs_7: 1, "
+            + "const_exprs_8: 1, "
+            + "const_exprs_9: 1, "
+            + "const_exprs_10: 1, "
+            + "addString1: hello world!, "
+            + "addString2: The quick brown fox jumps over the lazy dog., "
+            + "u: android.aidl.tests.Union.ns([1, 2, 3])"
+            + "}";
         assertThat(p.toString(), is(expected));
     }
 
diff --git a/tests/rust/test_client.rs b/tests/rust/test_client.rs
index b546528..6492782 100644
--- a/tests/rust/test_client.rs
+++ b/tests/rust/test_client.rs
@@ -22,7 +22,7 @@
     self, BpTestService, ITestServiceDefault,
 };
 use aidl_test_interface::aidl::android::aidl::tests::{
-    ByteEnum::ByteEnum, IntEnum::IntEnum, LongEnum::LongEnum, StructuredParcelable,
+    ByteEnum::ByteEnum, IntEnum::IntEnum, LongEnum::LongEnum, StructuredParcelable, Union,
 };
 use aidl_test_interface::binder;
 use aidl_test_versioned_interface::aidl::android::aidl::versioned::tests::IFooInterface::{
@@ -526,6 +526,8 @@
         parcelable.addString2,
         "The quick brown fox jumps over the lazy dog."
     );
+
+    assert_eq!(parcelable.u, Some(Union::Union::Ns(vec![1, 2, 3])))
 }
 
 const EXPECTED_ARG_VALUE: i32 = 100;
diff --git a/tests/rust/test_service.rs b/tests/rust/test_service.rs
index 5c32489..c6578b8 100644
--- a/tests/rust/test_service.rs
+++ b/tests/rust/test_service.rs
@@ -21,7 +21,7 @@
 };
 use aidl_test_interface::aidl::android::aidl::tests::{
     ByteEnum::ByteEnum, ConstantExpressionEnum::ConstantExpressionEnum, INamedCallback, INewName,
-    IOldName, IntEnum::IntEnum, LongEnum::LongEnum, StructuredParcelable,
+    IOldName, IntEnum::IntEnum, LongEnum::LongEnum, StructuredParcelable, Union,
 };
 use aidl_test_interface::binder::{self, Interface, ParcelFileDescriptor, SpIBinder};
 use aidl_test_versioned_interface_V1::aidl::android::aidl::versioned::tests::IFooInterface::{
@@ -269,6 +269,8 @@
         parcelable.const_exprs_9 = ConstantExpressionEnum::hexInt32_3;
         parcelable.const_exprs_10 = ConstantExpressionEnum::hexInt64_1;
 
+        parcelable.u = Some(Union::Union::Ns(vec![1, 2, 3]));
+
         Ok(())
     }