[ObjC] Add GenerateFile helper to factor out common code.

Few minor cleanups along the way.

PiperOrigin-RevId: 492546618
diff --git a/src/google/protobuf/compiler/objectivec/file.cc b/src/google/protobuf/compiler/objectivec/file.cc
index ed7b13c..887f81b 100644
--- a/src/google/protobuf/compiler/objectivec/file.cc
+++ b/src/google/protobuf/compiler/objectivec/file.cc
@@ -31,6 +31,7 @@
 #include "google/protobuf/compiler/objectivec/file.h"
 
 #include <algorithm>
+#include <functional>
 #include <iostream>
 #include <iterator>
 #include <memory>
@@ -237,220 +238,109 @@
   }
 }
 
-void FileGenerator::GenerateHeader(io::Printer* printer) const {
-  // The bundled protos (WKTs) don't use of forward declarations.
-  bool headers_use_forward_declarations =
-      generation_options_.headers_use_forward_declarations &&
-      !is_bundled_proto_;
+void FileGenerator::GenerateHeader(io::Printer* p) const {
+  GenerateFile(p, GeneratedFileType::kHeader, [&] {
+    p->Emit(
+        {
+            {"fwd_decls",
+             [&] {
+               absl::btree_set<std::string> fwd_decls;
+               for (const auto& generator : message_generators_) {
+                 generator->DetermineForwardDeclarations(
+                     &fwd_decls,
+                     /* include_external_types = */
+                     HeadersUseForwardDeclarations());
+               }
 
-  ImportWriter import_writer(
-      generation_options_.generate_for_named_framework,
-      generation_options_.named_framework_to_proto_path_mappings_path,
-      generation_options_.runtime_import_prefix,
-      /* for_bundled_proto = */ is_bundled_proto_);
-  const std::string header_extension(kHeaderExtension);
+               if (!fwd_decls.empty()) {
+                 p->Emit({{"decls", absl::StrJoin(fwd_decls, "\n")}},
+                         R"objc(
+                          $decls$
 
-  // Generated files bundled with the library get minimal imports, everything
-  // else gets the wrapper so everything is usable.
-  if (is_bundled_proto_) {
-    import_writer.AddRuntimeImport("GPBDescriptor.h");
-    import_writer.AddRuntimeImport("GPBMessage.h");
-    import_writer.AddRuntimeImport("GPBRootObject.h");
-  } else {
-    import_writer.AddRuntimeImport("GPBProtocolBuffers.h");
-  }
+                         )objc");
+               }
+             }},
+            {"enums",
+             [&] {
+               for (const auto& generator : enum_generators_) {
+                 generator->GenerateHeader(p);
+               }
+             }},
+            {"root_extensions",
+             [&] {
+               // The dynamic methods block is only needed if there are
+               // extensions that are file level scoped (not message scoped).
+               // The first file_->extension_count() of extension_generators_
+               // are the file scoped ones.
+               if (file_->extension_count()) {
+                 p->Emit(
+                     {
+                         {"extension_methods",
+                          [&] {
+                            for (int i = 0; i < file_->extension_count(); i++) {
+                              extension_generators_[i]->GenerateMembersHeader(
+                                  p);
+                            }
+                          }},
+                     },
+                     R"objc(
+                       @interface $root_class_name$ (DynamicMethods)
+                       $extension_methods$;
+                      @end
 
-  if (headers_use_forward_declarations) {
-    // #import any headers for "public imports" in the proto file.
-    for (int i = 0; i < file_->public_dependency_count(); i++) {
-      import_writer.AddFile(file_->public_dependency(i), header_extension);
-    }
-  } else {
-    for (int i = 0; i < file_->dependency_count(); i++) {
-      import_writer.AddFile(file_->dependency(i), header_extension);
-    }
-  }
+                     )objc");
+               }  // file_->extension_count()
+             }},
+            {"messages",
+             [&] {
+               for (const auto& generator : message_generators_) {
+                 generator->GenerateMessageHeader(p);
+               }
+             }},
+        },
+        R"objc(
+          CF_EXTERN_C_BEGIN
 
-  printer->Emit(
-      {
-          // Avoid the directive within the string below being seen by the
-          // tool.
-          {"clangfmt", "clang-format"},
-          {"filename", file_->name()},
-          // For extensions to chain together, the Root gets created even if
-          // there are no extensions.
-          {"root_class_name", root_class_name_},
-          {"runtime_imports",
-           [&] {
-             import_writer.EmitRuntimeImports(
-                 printer, /* default_cpp_symbol = */ !is_bundled_proto_);
-           }},
-          // Add some verification that the generated code matches the source
-          // the code is being compiled with.
-          //
-          // NOTE: Where used, this captures the raw numeric values at the
-          // time the generator was compiled, since that will be the versions
-          // for the ObjC runtime at that time.  The constants in the
-          // generated code will then get their values at at compile time (so
-          // checking against the headers being used to compile).
-          {"google_protobuf_objc_version", GOOGLE_PROTOBUF_OBJC_VERSION},
-          {"file_imports", [&] { import_writer.EmitFileImports(printer); }},
-          {"fwd_decls",
-           [&] {
-             absl::btree_set<std::string> fwd_decls;
-             for (const auto& generator : message_generators_) {
-               generator->DetermineForwardDeclarations(
-                   &fwd_decls,
-                   /* include_external_types = */
-                   headers_use_forward_declarations);
-             }
+          $fwd_decls$;
+          NS_ASSUME_NONNULL_BEGIN
 
-             if (!fwd_decls.empty()) {
-               printer->Emit({{"decls", absl::StrJoin(fwd_decls, "\n")}},
-                             R"objc(
-                               $decls$
+          $enums$;
+          #pragma mark - $root_class_name$
 
-                             )objc");
-             }
-           }},
-          {"enums",
-           [&] {
-             for (const auto& generator : enum_generators_) {
-               generator->GenerateHeader(printer);
-             }
-           }},
-          {"root_extensions",
-           [&] {
-             // The dynamic methods block is only needed if there are
-             // extensions that are file level scoped (not message scoped).
-             // The first file_->extension_count() of extension_generators_
-             // are the file scoped ones.
-             if (file_->extension_count()) {
-               printer->Emit(
-                   {
-                       {"extension_methods",
-                        [&] {
-                          for (int i = 0; i < file_->extension_count(); i++) {
-                            extension_generators_[i]->GenerateMembersHeader(
-                                printer);
-                          }
-                        }},
-                   },
-                   R"objc(
-                    @interface $root_class_name$ (DynamicMethods)
-                    $extension_methods$;
-                    @end
+          /**
+           * Exposes the extension registry for this file.
+           *
+           * The base class provides:
+           * @code
+           *   + (GPBExtensionRegistry *)extensionRegistry;
+           * @endcode
+           * which is a @c GPBExtensionRegistry that includes all the extensions defined by
+           * this file and all files that it depends on.
+           **/
+          GPB_FINAL @interface $root_class_name$ : GPBRootObject
+          @end
 
-                  )objc");
-             }  // file_->extension_count()
-           }},
-          {"messages",
-           [&] {
-             for (const auto& generator : message_generators_) {
-               generator->GenerateMessageHeader(printer);
-             }
-           }},
-      },
-      R"objc(
-        // Generated by the protocol buffer compiler.  DO NOT EDIT!
-        // $clangfmt$ off
-        // source: $filename$
+          $root_extensions$;
+          $messages$;
+          NS_ASSUME_NONNULL_END
 
-        $runtime_imports$
-
-        #if GOOGLE_PROTOBUF_OBJC_VERSION < $google_protobuf_objc_version$
-        #error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.
-        #endif
-        #if $google_protobuf_objc_version$ < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION
-        #error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.
-        #endif
-
-        $file_imports$
-        // @@protoc_insertion_point(imports)
-
-        #pragma clang diagnostic push
-        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
-
-        CF_EXTERN_C_BEGIN
-
-        $fwd_decls$;
-        NS_ASSUME_NONNULL_BEGIN
-
-        $enums$;
-        #pragma mark - $root_class_name$
-
-        /**
-         * Exposes the extension registry for this file.
-         *
-         * The base class provides:
-         * @code
-         *   + (GPBExtensionRegistry *)extensionRegistry;
-         * @endcode
-         * which is a @c GPBExtensionRegistry that includes all the extensions defined by
-         * this file and all files that it depends on.
-         **/
-        GPB_FINAL @interface $root_class_name$ : GPBRootObject
-        @end
-
-        $root_extensions$;
-        $messages$;
-        NS_ASSUME_NONNULL_END
-
-        CF_EXTERN_C_END
-
-        #pragma clang diagnostic pop
-
-        // @@protoc_insertion_point(global_scope)
-
-        // $clangfmt$ on
-      )objc");
+          CF_EXTERN_C_END
+        )objc");
+  });
 }
 
-void FileGenerator::GenerateSource(io::Printer* printer) const {
-  ImportWriter import_writer(
-      generation_options_.generate_for_named_framework,
-      generation_options_.named_framework_to_proto_path_mappings_path,
-      generation_options_.runtime_import_prefix,
-      /* for_bundled_proto = */ is_bundled_proto_);
-  const std::string header_extension(kHeaderExtension);
-
-  // #import the runtime support.
-  import_writer.AddRuntimeImport("GPBProtocolBuffers_RuntimeSupport.h");
-  // #import the header for this proto file.
-  import_writer.AddFile(file_, header_extension);
-
+void FileGenerator::GenerateSource(io::Printer* p) const {
   std::vector<const FileDescriptor*> deps_with_extensions =
       common_state_->CollectMinimalFileDepsContainingExtensions(file_);
 
-  // The bundled protos (WKTs) don't use of forward declarations.
-  bool headers_use_forward_declarations =
-      generation_options_.headers_use_forward_declarations &&
-      !is_bundled_proto_;
-
-  if (headers_use_forward_declarations) {
-    // #import the headers for anything that a plain dependency of this proto
-    // file (that means they were just an include, not a "public" include).
-    absl::flat_hash_set<std::string> public_import_names;
-    for (int i = 0; i < file_->public_dependency_count(); i++) {
-      public_import_names.insert(file_->public_dependency(i)->name());
-    }
-    for (int i = 0; i < file_->dependency_count(); i++) {
-      const FileDescriptor* dep = file_->dependency(i);
-      if (!public_import_names.contains(dep->name())) {
-        import_writer.AddFile(dep, header_extension);
-      }
-    }
-  }
-
   // If any indirect dependency provided extensions, it needs to be directly
   // imported so it can get merged into the root's extensions registry.
   // See the Note by CollectMinimalFileDepsContainingExtensions before
   // changing this.
-  for (std::vector<const FileDescriptor*>::iterator iter =
-           deps_with_extensions.begin();
-       iter != deps_with_extensions.end(); ++iter) {
-    if (!IsDirectDependency(*iter, file_)) {
-      import_writer.AddFile(*iter, header_extension);
+  std::vector<const FileDescriptor*> extra_files;
+  for (auto& dep : deps_with_extensions) {
+    if (!IsDirectDependency(dep, file_)) {
+      extra_files.push_back(dep);
     }
   }
 
@@ -462,73 +352,178 @@
     generator->DetermineObjectiveCClassDefinitions(&fwd_decls);
   }
 
-  printer->Emit(
+  std::vector<std::string> ignored_warnings;
+  // The generated code for oneof's uses direct ivar access, suppress the
+  // warning in case developer turn that on in the context they compile the
+  // generated code.
+  for (const auto& generator : message_generators_) {
+    if (generator->IncludesOneOfDefinition()) {
+      ignored_warnings.push_back("direct-ivar-access");
+      break;
+    }
+  }
+  if (!fwd_decls.empty()) {
+    ignored_warnings.push_back("dollar-in-identifier-extension");
+  }
+
+  GenerateFile(
+      p, GeneratedFileType::kSource, ignored_warnings, extra_files, [&] {
+        p->Emit(
+            {
+                {"fwd_decls",
+                 [&] {
+                   if (fwd_decls.empty()) {
+                     return;
+                   }
+                   p->Emit({{"decls", absl::StrJoin(fwd_decls, "\n")}},
+                           R"objc(
+                             #pragma mark - Objective C Class declarations
+                             // Forward declarations of Objective C classes that we can use as
+                             // static values in struct initializers.
+                             // We don't use [Foo class] because it is not a static value.
+                             $decls$
+
+                           )objc");
+                 }},
+                {"root_implementation",
+                 [&] { EmitRootImplementation(p, deps_with_extensions); }},
+                {"file_descriptor_implementation",
+                 [&] { EmitFileDescriptorImplementation(p); }},
+                {"enums_and_messages",
+                 [&] {
+                   for (const auto& generator : enum_generators_) {
+                     generator->GenerateSource(p);
+                   }
+                   for (const auto& generator : message_generators_) {
+                     generator->GenerateSource(p);
+                   }
+                 }},
+            },
+            R"objc(
+              $fwd_decls$;
+              $root_implementation$
+
+              $file_descriptor_implementation$;
+              $enums_and_messages$;
+            )objc");
+      });
+}
+
+void FileGenerator::GenerateFile(
+    io::Printer* p, GeneratedFileType file_type,
+    const std::vector<std::string>& ignored_warnings,
+    const std::vector<const FileDescriptor*>& extra_files_to_import,
+    std::function<void()> body) const {
+  ImportWriter import_writer(
+      generation_options_.generate_for_named_framework,
+      generation_options_.named_framework_to_proto_path_mappings_path,
+      generation_options_.runtime_import_prefix,
+      /* for_bundled_proto = */ is_bundled_proto_);
+  const std::string header_extension(kHeaderExtension);
+
+  switch (file_type) {
+    case GeneratedFileType::kHeader:
+      // Generated files bundled with the library get minimal imports,
+      // everything else gets the wrapper so everything is usable.
+      if (is_bundled_proto_) {
+        import_writer.AddRuntimeImport("GPBDescriptor.h");
+        import_writer.AddRuntimeImport("GPBMessage.h");
+        import_writer.AddRuntimeImport("GPBRootObject.h");
+      } else {
+        import_writer.AddRuntimeImport("GPBProtocolBuffers.h");
+      }
+      if (HeadersUseForwardDeclarations()) {
+        // #import any headers for "public imports" in the proto file.
+        for (int i = 0; i < file_->public_dependency_count(); i++) {
+          import_writer.AddFile(file_->public_dependency(i), header_extension);
+        }
+      } else {
+        for (int i = 0; i < file_->dependency_count(); i++) {
+          import_writer.AddFile(file_->dependency(i), header_extension);
+        }
+      }
+      break;
+    case GeneratedFileType::kSource:
+      import_writer.AddRuntimeImport("GPBProtocolBuffers_RuntimeSupport.h");
+      import_writer.AddFile(file_, header_extension);
+      if (HeadersUseForwardDeclarations()) {
+        // #import the headers for anything that a plain dependency of this
+        // proto file (that means they were just an include, not a "public"
+        // include).
+        absl::flat_hash_set<std::string> public_import_names;
+        for (int i = 0; i < file_->public_dependency_count(); i++) {
+          public_import_names.insert(file_->public_dependency(i)->name());
+        }
+        for (int i = 0; i < file_->dependency_count(); i++) {
+          const FileDescriptor* dep = file_->dependency(i);
+          if (!public_import_names.contains(dep->name())) {
+            import_writer.AddFile(dep, header_extension);
+          }
+        }
+      }
+      break;
+  }
+
+  for (const auto& dep : extra_files_to_import) {
+    import_writer.AddFile(dep, header_extension);
+  }
+
+  p->Emit(
       {
           // Avoid the directive within the string below being seen by the
           // tool.
           {"clangfmt", "clang-format"},
           {"filename", file_->name()},
           {"root_class_name", root_class_name_},
+          {"source_check",
+           [&] {
+             if (file_type == GeneratedFileType::kHeader) {
+               // Add some verification that the generated code matches the
+               // source the code is being compiled with.
+               //
+               // NOTE: Where used, this captures the raw numeric values at the
+               // time the generator was compiled, since that will be the
+               // versions for the ObjC runtime at that time.  The constants in
+               // the generated code will then get their values at at compile
+               // time (so checking against the headers being used to compile).
+               p->Emit({{"google_protobuf_objc_version",
+                         GOOGLE_PROTOBUF_OBJC_VERSION}},
+                       R"objc(
+    #if GOOGLE_PROTOBUF_OBJC_VERSION < $google_protobuf_objc_version$
+    #error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.
+    #endif
+    #if $google_protobuf_objc_version$ < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION
+    #error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.
+    #endif
+
+                       )objc");
+             }
+           }},
           {"runtime_imports",
            [&] {
              import_writer.EmitRuntimeImports(
-                 printer, /* default_cpp_symbol = */ !is_bundled_proto_);
+                 p, /* default_cpp_symbol = */ !is_bundled_proto_);
            }},
-          {"file_imports", [&] { import_writer.EmitFileImports(printer); }},
+          {"file_imports", [&] { import_writer.EmitFileImports(p); }},
           {"extra_imports",
            [&] {
-             // Enums use atomic in the generated code, so add the system import
-             // as needed.
-             if (!enum_generators_.empty()) {
-               printer->Emit("#import <stdatomic.h>\n\n");
+             // Enum implementation uses atomic in the generated code, so add
+             // the system import as needed.
+             if (file_type == GeneratedFileType::kSource &&
+                 !enum_generators_.empty()) {
+               p->Emit("#import <stdatomic.h>\n\n");
              }
            }},
-          {"extra_pragmas",
+          {"extra_warnings",
            [&] {
-             // The generated code for oneof's uses direct ivar access, suppress
-             // the warning in case developer turn that on in the context they
-             // compile the generated code.
-             for (const auto& generator : message_generators_) {
-               if (generator->IncludesOneOfDefinition()) {
-                 printer->Emit(R"objc(
-                     #pragma clang diagnostic ignored "-Wdirect-ivar-access"
-                   )objc");
-                 break;
-               }
-             }
-             if (!fwd_decls.empty()) {
-               printer->Emit(R"objc(
-                   #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
-                 )objc");
+             for (const auto& warning : ignored_warnings) {
+               p->Emit({{"warning", warning}},
+                       R"objc(
+                        #pragma clang diagnostic ignored "-W$warning$"
+                      )objc");
              }
            }},
-          {"fwd_decls",
-           [&] {
-             if (!fwd_decls.empty()) {
-               printer->Emit({{"decls", absl::StrJoin(fwd_decls, "\n")}},
-                             R"objc(
-                     #pragma mark - Objective C Class declarations
-                     // Forward declarations of Objective C classes that we can use as
-                     // static values in struct initializers.
-                     // We don't use [Foo class] because it is not a static value.
-                     $decls$
-
-                   )objc");
-             }
-           }},
-          {"root_implementation",
-           [&] { EmitRootImplementation(printer, deps_with_extensions); }},
-          {"file_descriptor_implementation",
-           [&] { EmitFileDescriptorImplementation(printer); }},
-          {"enums_and_messages",
-           [&] {
-             for (const auto& generator : enum_generators_) {
-               generator->GenerateSource(printer);
-             }
-             for (const auto& generator : message_generators_) {
-               generator->GenerateSource(printer);
-             }
-           }},
+          {"body", body},
       },
       R"objc(
         // Generated by the protocol buffer compiler.  DO NOT EDIT!
@@ -537,26 +532,23 @@
 
         $runtime_imports$
 
+        $source_check$;
         $extra_imports$;
         $file_imports$
         // @@protoc_insertion_point(imports)
 
         #pragma clang diagnostic push
         #pragma clang diagnostic ignored "-Wdeprecated-declarations"
-        $extra_pragmas$
+        $extra_warnings$;
 
-        $fwd_decls$;
-        $root_implementation$
-
-        $file_descriptor_implementation$;
-        $enums_and_messages$;
+        $body$;
 
         #pragma clang diagnostic pop
 
         // @@protoc_insertion_point(global_scope)
 
         // $clangfmt$ on
-      )objc");
+  )objc");
 }
 
 void FileGenerator::EmitRootImplementation(
@@ -564,13 +556,12 @@
     const std::vector<const FileDescriptor*>& deps_with_extensions) const {
   p->Emit(
       {
-          {"root_class_name", root_class_name_},
           {"root_extension_registry",
            [&] {
              // If there were any extensions or this file has any dependencies,
              // output a registry to override to create the file specific
              // registry.
-             if (!!extension_generators_.empty() &&
+             if (extension_generators_.empty() &&
                  deps_with_extensions.empty()) {
                if (file_->dependency_count() == 0) {
                  p->Emit(R"objc(
@@ -638,8 +629,8 @@
                  )objc");
              } else {
                p->Emit(R"objc(
-                     // Merge in the imports (direct or indirect) that defined extensions.
-                   )objc");
+                   // Merge in the imports (direct or indirect) that defined extensions.
+                 )objc");
                for (const auto& dep : deps_with_extensions) {
                  p->Emit({{"dependency", FileClassName(dep)}},
                          R"objc(
@@ -687,7 +678,6 @@
 
   p->Emit(
       {
-          {"root_class_name", root_class_name_},
           {"package", file_->package()},
           {"objc_prefix", objc_prefix},
           {"objc_prefix_arg",
diff --git a/src/google/protobuf/compiler/objectivec/file.h b/src/google/protobuf/compiler/objectivec/file.h
index c60f067..a6bed27 100644
--- a/src/google/protobuf/compiler/objectivec/file.h
+++ b/src/google/protobuf/compiler/objectivec/file.h
@@ -31,6 +31,7 @@
 #ifndef GOOGLE_PROTOBUF_COMPILER_OBJECTIVEC_FILE_H__
 #define GOOGLE_PROTOBUF_COMPILER_OBJECTIVEC_FILE_H__
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <vector>
@@ -83,6 +84,17 @@
   void GenerateHeader(io::Printer* p) const;
   void GenerateSource(io::Printer* p) const;
 
+  enum class GeneratedFileType : int { kHeader, kSource };
+
+  void GenerateFile(io::Printer* p, GeneratedFileType file_type,
+                    const std::vector<std::string>& ignored_warnings,
+                    const std::vector<const FileDescriptor*>& extra_files,
+                    std::function<void()> body) const;
+  void GenerateFile(io::Printer* p, GeneratedFileType file_type,
+                    std::function<void()> body) const {
+    GenerateFile(p, file_type, {}, {}, body);
+  }
+
   void EmitRootImplementation(
       io::Printer* p,
       const std::vector<const FileDescriptor*>& deps_with_extensions) const;
@@ -91,6 +103,12 @@
       const std::vector<const FileDescriptor*>& deps_with_extensions) const;
   void EmitFileDescriptorImplementation(io::Printer* p) const;
 
+  bool HeadersUseForwardDeclarations() const {
+    // The bundled protos (WKTs) don't make use of forward declarations.
+    return !is_bundled_proto_ &&
+           generation_options_.headers_use_forward_declarations;
+  }
+
  private:
   const FileDescriptor* file_;
   const GenerationOptions& generation_options_;
diff --git a/src/google/protobuf/compiler/objectivec/import_writer.cc b/src/google/protobuf/compiler/objectivec/import_writer.cc
index 10af534..eef7636 100644
--- a/src/google/protobuf/compiler/objectivec/import_writer.cc
+++ b/src/google/protobuf/compiler/objectivec/import_writer.cc
@@ -212,7 +212,7 @@
              }},
         },
         R"objc(
-          $imports$;
+          $imports$
         )objc");
 
     return;
@@ -234,7 +234,7 @@
              }},
         },
         R"objc(
-          $imports$;
+          $imports$
         )objc");
     return;
   }