clang_generator: Correctly emit nested C-like structs

- Add more tests for this

PiperOrigin-RevId: 435296715
Change-Id: I7b42dbc58dc054d2565af9ad22498d98416b7af7
diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc
index 2b6c644..5e6381c 100644
--- a/sandboxed_api/tools/clang_generator/emitter.cc
+++ b/sandboxed_api/tools/clang_generator/emitter.cc
@@ -238,10 +238,12 @@
   }
 
   if (const auto* record_decl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
-    // For C++ classes/structs, only emit a forward declaration.
-    return absl::StrCat(PrintRecordTemplateArguments(record_decl),
-                        record_decl->isClass() ? "class " : "struct ",
-                        ToStringView(record_decl->getName()));
+    if (!record_decl->isCLike()) {
+      // For C++ classes/structs, only emit a forward declaration.
+      return absl::StrCat(PrintRecordTemplateArguments(record_decl),
+                          record_decl->isClass() ? "class " : "struct ",
+                          ToStringView(record_decl->getName()));
+    }
   }
   return PrintDecl(decl);
 }
@@ -423,8 +425,7 @@
         return;
       }
     }
-    ns_name = absl::StrCat(ns_path[0].empty() ? "" : " ",
-                           absl::StrJoin(ns_path, "::"));
+    ns_name = absl::StrJoin(ns_path, "::");
   }
 
   rendered_types_[ns_name].push_back(GetSpelling(decl));
diff --git a/sandboxed_api/tools/clang_generator/emitter_test.cc b/sandboxed_api/tools/clang_generator/emitter_test.cc
index 42739c7..9fb82b9 100644
--- a/sandboxed_api/tools/clang_generator/emitter_test.cc
+++ b/sandboxed_api/tools/clang_generator/emitter_test.cc
@@ -93,10 +93,68 @@
   }
   EXPECT_THAT(ugly_types,
               ElementsAre("typedef enum { kRed, kGreen, kBlue } Color",
-                          "struct Channel", "struct ByValue",
+                          "struct Channel {"
+                          " Color color;"
+                          " std::size_t width;"
+                          " std::size_t height; }",
+                          "struct ByValue { int value; }",
                           "typedef struct { int member; } MyStruct"));
 }
 
+TEST_F(EmitterTest, NestedStruct) {
+  EmitterForTesting emitter;
+  RunFrontendAction(
+      R"(
+    struct A {
+      struct B {
+         int number;
+      };
+      B b;
+      int data;
+    };
+    extern "C" void Structize(A* s);
+  )",
+      absl::make_unique<GeneratorAction>(emitter, GeneratorOptions()));
+
+  std::vector<std::string> ugly_types;
+  for (const auto& type : emitter.rendered_types_[""]) {
+    ugly_types.push_back(Uglify(type));
+  }
+  EXPECT_THAT(ugly_types, ElementsAre("struct A {"
+                                      " struct B {"
+                                      " int number;"
+                                      " };"
+                                      " A::B b;"
+                                      " int data; "
+                                      "}"));
+}
+
+TEST_F(EmitterTest, NestedAnonymousStruct) {
+  EmitterForTesting emitter;
+  RunFrontendAction(
+      R"(
+    struct A {
+      struct {
+         int number;
+      } b;
+      int data;
+    };
+    extern "C" void Structize(A* s);
+  )",
+      absl::make_unique<GeneratorAction>(emitter, GeneratorOptions()));
+
+  std::vector<std::string> ugly_types;
+  for (const auto& type : emitter.rendered_types_[""]) {
+    ugly_types.push_back(Uglify(type));
+  }
+  EXPECT_THAT(ugly_types, ElementsAre("struct A {"
+                                      " struct {"
+                                      " int number;"
+                                      " } b;"
+                                      " int data; "
+                                      "}"));
+}
+
 TEST(IncludeGuard, CreatesRandomizedGuardForEmptyFilename) {
   // Copybara will transform the string. This is intentional.
   constexpr absl::string_view kGeneratedHeaderPrefix =