[ObjC] Move GenerateHeader over to Printer::Emit.

Update ImportWriter to have a second Emit api for the runtime files to
streamline things a bit better.

PiperOrigin-RevId: 492210147
diff --git a/objectivec/GPBAny.pbobjc.h b/objectivec/GPBAny.pbobjc.h
index cb2b4f7..bb19e5f 100644
--- a/objectivec/GPBAny.pbobjc.h
+++ b/objectivec/GPBAny.pbobjc.h
@@ -177,4 +177,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBApi.pbobjc.h b/objectivec/GPBApi.pbobjc.h
index 877a0b5..bbae2ed 100644
--- a/objectivec/GPBApi.pbobjc.h
+++ b/objectivec/GPBApi.pbobjc.h
@@ -298,4 +298,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBDuration.pbobjc.h b/objectivec/GPBDuration.pbobjc.h
index 223d262..05c63d8 100644
--- a/objectivec/GPBDuration.pbobjc.h
+++ b/objectivec/GPBDuration.pbobjc.h
@@ -133,4 +133,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBEmpty.pbobjc.h b/objectivec/GPBEmpty.pbobjc.h
index d027837..3de240d 100644
--- a/objectivec/GPBEmpty.pbobjc.h
+++ b/objectivec/GPBEmpty.pbobjc.h
@@ -60,4 +60,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBFieldMask.pbobjc.h b/objectivec/GPBFieldMask.pbobjc.h
index db4e76a..3e7d349 100644
--- a/objectivec/GPBFieldMask.pbobjc.h
+++ b/objectivec/GPBFieldMask.pbobjc.h
@@ -261,4 +261,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBSourceContext.pbobjc.h b/objectivec/GPBSourceContext.pbobjc.h
index 6736828..a55939a 100644
--- a/objectivec/GPBSourceContext.pbobjc.h
+++ b/objectivec/GPBSourceContext.pbobjc.h
@@ -65,4 +65,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBStruct.pbobjc.h b/objectivec/GPBStruct.pbobjc.h
index 2196639..9bedafc 100644
--- a/objectivec/GPBStruct.pbobjc.h
+++ b/objectivec/GPBStruct.pbobjc.h
@@ -192,4 +192,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBTimestamp.pbobjc.h b/objectivec/GPBTimestamp.pbobjc.h
index e0566da..a374a0a 100644
--- a/objectivec/GPBTimestamp.pbobjc.h
+++ b/objectivec/GPBTimestamp.pbobjc.h
@@ -162,4 +162,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBType.pbobjc.h b/objectivec/GPBType.pbobjc.h
index 02388b0..6eb1d04 100644
--- a/objectivec/GPBType.pbobjc.h
+++ b/objectivec/GPBType.pbobjc.h
@@ -432,4 +432,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/objectivec/GPBWrappers.pbobjc.h b/objectivec/GPBWrappers.pbobjc.h
index 463bc57..11e220b 100644
--- a/objectivec/GPBWrappers.pbobjc.h
+++ b/objectivec/GPBWrappers.pbobjc.h
@@ -207,4 +207,4 @@
 
 // @@protoc_insertion_point(global_scope)
 
-// clange-format on
+// clang-format on
diff --git a/src/google/protobuf/compiler/objectivec/enum_field.cc b/src/google/protobuf/compiler/objectivec/enum_field.cc
index e5e881a..7e425c7 100644
--- a/src/google/protobuf/compiler/objectivec/enum_field.cc
+++ b/src/google/protobuf/compiler/objectivec/enum_field.cc
@@ -134,7 +134,7 @@
       !IsProtobufLibraryBundledProtoFile(descriptor_->enum_type()->file())) {
     // Enum name is already in "storage_type".
     const std::string& name = variable("storage_type");
-    fwd_decls->insert("GPB_ENUM_FWD_DECLARE(" + name + ")");
+    fwd_decls->insert(absl::StrCat("GPB_ENUM_FWD_DECLARE(", name, ");"));
   }
 }
 
diff --git a/src/google/protobuf/compiler/objectivec/file.cc b/src/google/protobuf/compiler/objectivec/file.cc
index efe2443..15ec93d 100644
--- a/src/google/protobuf/compiler/objectivec/file.cc
+++ b/src/google/protobuf/compiler/objectivec/file.cc
@@ -44,6 +44,7 @@
 #include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "google/protobuf/compiler/objectivec/enum.h"
 #include "google/protobuf/compiler/objectivec/extension.h"
 #include "google/protobuf/compiler/objectivec/import_writer.h"
@@ -242,155 +243,172 @@
 }
 
 void FileGenerator::GenerateHeader(io::Printer* printer) const {
-  std::vector<std::string> headers;
-  // Generated files bundled with the library get minimal imports, everything
-  // else gets the wrapper so everything is usable.
-  if (is_bundled_proto_) {
-    headers.push_back("GPBDescriptor.h");
-    headers.push_back("GPBMessage.h");
-    headers.push_back("GPBRootObject.h");
-    for (int i = 0; i < file_->dependency_count(); i++) {
-      const std::string header_name = BundledFileName(file_->dependency(i));
-      headers.push_back(header_name);
-    }
-  } else {
-    headers.push_back("GPBProtocolBuffers.h");
-  }
-  PrintFileRuntimePreamble(printer, headers);
-
-  // Add some verification that the generated code matches the source the
-  // code is being compiled with.
-  // NOTE: 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).
-  // clang-format off
-  printer->Print(
-      "#if GOOGLE_PROTOBUF_OBJC_VERSION < $google_protobuf_objc_version$\n"
-      "#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.\n"
-      "#endif\n"
-      "#if $google_protobuf_objc_version$ < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION\n"
-      "#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.\n"
-      "#endif\n"
-      "\n",
-      "google_protobuf_objc_version", absl::StrCat(GOOGLE_PROTOBUF_OBJC_VERSION));
-  // clang-format on
-
   // The bundled protos (WKTs) don't use of forward declarations.
   bool headers_use_forward_declarations =
       generation_options_.headers_use_forward_declarations &&
       !is_bundled_proto_;
 
-  {
-    ImportWriter import_writer(
-        generation_options_.generate_for_named_framework,
-        generation_options_.named_framework_to_proto_path_mappings_path,
-        generation_options_.runtime_import_prefix,
-        /* include_wkt_imports = */ false);
-    const std::string header_extension(kHeaderExtension);
-    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);
-      }
+  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);
+
+  // 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 (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);
     }
-    import_writer.Emit(printer);
-  }
-
-  // Note:
-  //  deprecated-declarations suppression is only needed if some place in this
-  //    proto file is something deprecated or if it references something from
-  //    another file that is deprecated.
-  // clang-format off
-  printer->Print(
-      "// @@protoc_insertion_point(imports)\n"
-      "\n"
-      "#pragma clang diagnostic push\n"
-      "#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n"
-      "\n"
-      "CF_EXTERN_C_BEGIN\n"
-      "\n");
-  // clang-format on
-
-  absl::btree_set<std::string> fwd_decls;
-  for (const auto& generator : message_generators_) {
-    generator->DetermineForwardDeclarations(
-        &fwd_decls,
-        /* include_external_types = */ headers_use_forward_declarations);
-  }
-  for (const auto& fwd_decl : fwd_decls) {
-    printer->Print("$value$;\n", "value", fwd_decl);
-  }
-  if (fwd_decls.begin() != fwd_decls.end()) {
-    printer->Print("\n");
-  }
-
-  printer->Print(
-      "NS_ASSUME_NONNULL_BEGIN\n"
-      "\n");
-
-  // need to write out all enums first
-  for (const auto& generator : enum_generators_) {
-    generator->GenerateHeader(printer);
-  }
-
-  // For extensions to chain together, the Root gets created even if there
-  // are no extensions.
-  printer->Print(
-      // clang-format off
-      "#pragma mark - $root_class_name$\n"
-      "\n"
-      "/**\n"
-      " * Exposes the extension registry for this file.\n"
-      " *\n"
-      " * The base class provides:\n"
-      " * @code\n"
-      " *   + (GPBExtensionRegistry *)extensionRegistry;\n"
-      " * @endcode\n"
-      " * which is a @c GPBExtensionRegistry that includes all the extensions defined by\n"
-      " * this file and all files that it depends on.\n"
-      " **/\n"
-      "GPB_FINAL @interface $root_class_name$ : GPBRootObject\n"
-      "@end\n"
-      "\n",
-      // clang-format off
-      "root_class_name", root_class_name_);
-
-  // 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->Print(
-        "@interface $root_class_name$ (DynamicMethods)\n",
-        "root_class_name", root_class_name_);
-
-    for (int i = 0; i < file_->extension_count(); i++) {
-      extension_generators_[i]->GenerateMembersHeader(printer);
+  } else {
+    for (int i = 0; i < file_->dependency_count(); i++) {
+      import_writer.AddFile(file_->dependency(i), header_extension);
     }
-
-    printer->Print("@end\n\n");
-  }  // file_->extension_count()
-
-  for (const auto& generator : message_generators_) {
-    generator->GenerateMessageHeader(printer);
   }
 
-  // clang-format off
-  printer->Print(
-      "NS_ASSUME_NONNULL_END\n"
-      "\n"
-      "CF_EXTERN_C_END\n"
-      "\n"
-      "#pragma clang diagnostic pop\n"
-      "\n"
-      "// @@protoc_insertion_point(global_scope)\n"
-      "\n"
-      "// clange-format on\n");
-  // clang-format on
+  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);
+             }
+
+             if (!fwd_decls.empty()) {
+               printer->Emit({{"decls", absl::StrJoin(fwd_decls, "\n")}},
+                             R"objc(
+                               $decls$
+
+                             )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
+
+                  )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$
+
+        $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");
 }
 
 void FileGenerator::GenerateSource(io::Printer* printer) const {
@@ -455,7 +473,7 @@
       }
     }
 
-    import_writer.Emit(printer);
+    import_writer.EmitFileImports(printer);
   }
 
   bool includes_oneof = false;
@@ -712,7 +730,8 @@
   } else {
     ImportWriter::EmitRuntimeImports(printer, headers_to_import,
                                      generation_options_.runtime_import_prefix,
-                                     true);
+                                     /* is_bundled_proto = */ false,
+                                     /* default_cpp_symbol = */ true);
   }
 
   printer->Print("\n");
diff --git a/src/google/protobuf/compiler/objectivec/import_writer.cc b/src/google/protobuf/compiler/objectivec/import_writer.cc
index 6ddb4f6..1450166 100644
--- a/src/google/protobuf/compiler/objectivec/import_writer.cc
+++ b/src/google/protobuf/compiler/objectivec/import_writer.cc
@@ -118,12 +118,12 @@
 ImportWriter::ImportWriter(
     const std::string& generate_for_named_framework,
     const std::string& named_framework_to_proto_path_mappings_path,
-    const std::string& runtime_import_prefix, bool include_wkt_imports)
+    const std::string& runtime_import_prefix, bool for_bundled_proto)
     : generate_for_named_framework_(generate_for_named_framework),
       named_framework_to_proto_path_mappings_path_(
           named_framework_to_proto_path_mappings_path),
       runtime_import_prefix_(runtime_import_prefix),
-      include_wkt_imports_(include_wkt_imports),
+      for_bundled_proto_(for_bundled_proto),
       need_to_parse_mapping_file_(true) {}
 
 void ImportWriter::AddFile(const FileDescriptor* file,
@@ -132,7 +132,7 @@
     // The imports of the WKTs are only needed within the library itself,
     // in other cases, they get skipped because the generated code already
     // import GPBProtocolBuffers.h and hence proves them.
-    if (include_wkt_imports_) {
+    if (for_bundled_proto_) {
       const std::string header_name =
           "GPB" + FilePathBasename(file) + header_extension;
       protobuf_imports_.push_back(header_name);
@@ -162,15 +162,13 @@
   other_imports_.push_back(FilePath(file) + header_extension);
 }
 
-void ImportWriter::Emit(io::Printer* p) const {
+void ImportWriter::AddRuntimeImport(const std::string& header_name) {
+  protobuf_imports_.push_back(header_name);
+}
+
+void ImportWriter::EmitFileImports(io::Printer* p) const {
   p->Emit(
       {
-          {"runtime_import",
-           [&] {
-             if (!protobuf_imports_.empty()) {
-               EmitRuntimeImports(p, protobuf_imports_, runtime_import_prefix_);
-             }
-           }},
           {"other_framework_imports",
            [&] {
              for (const auto& header : other_framework_imports_) {
@@ -191,15 +189,21 @@
            }},
       },
       R"objc(
-        $runtime_import$;
         $other_framework_imports$;
         $other_imports$;
       )objc");
 }
 
+void ImportWriter::EmitRuntimeImports(io::Printer* p,
+                                      bool default_cpp_symbol) const {
+  EmitRuntimeImports(p, protobuf_imports_, runtime_import_prefix_,
+                     for_bundled_proto_, default_cpp_symbol);
+}
+
 void ImportWriter::EmitRuntimeImports(
-    io::Printer* p, const std::vector<std::string>& header_to_import,
-    const std::string& runtime_import_prefix, bool default_cpp_symbol) {
+    io::Printer* p, const std::vector<std::string>& headers_to_import,
+    const std::string& runtime_import_prefix, bool is_bundled_proto,
+    bool default_cpp_symbol) {
   // Given an override, use that.
   if (!runtime_import_prefix.empty()) {
     p->Emit(
@@ -207,7 +211,7 @@
             {"import_prefix", runtime_import_prefix},
             {"imports",
              [&] {
-               for (const auto& header : header_to_import) {
+               for (const auto& header : headers_to_import) {
                  p->Emit({{"header", header}},
                          R"objc(
                            #import "$import_prefix$/$header$"
@@ -222,6 +226,27 @@
     return;
   }
 
+  // If bundled, no need to do the framework support below.
+  if (is_bundled_proto) {
+    GOOGLE_CHECK(!default_cpp_symbol);
+    p->Emit(
+        {
+            {"imports",
+             [&] {
+               for (const auto& header : headers_to_import) {
+                 p->Emit({{"header", header}},
+                         R"objc(
+                           #import "$header$"
+                         )objc");
+               }
+             }},
+        },
+        R"objc(
+          $imports$;
+        )objc");
+    return;
+  }
+
   auto v = p->WithVars({
       {"cpp_symbol",
        ProtobufFrameworkImportSymbol(ProtobufLibraryFrameworkName)},
@@ -244,7 +269,7 @@
           {"framework_name", ProtobufLibraryFrameworkName},
           {"framework_imports",
            [&] {
-             for (const auto& header : header_to_import) {
+             for (const auto& header : headers_to_import) {
                p->Emit({{"header", header}},
                        R"objc(
                          #import <$framework_name$/$header$>
@@ -253,7 +278,7 @@
            }},
           {"raw_imports",
            [&] {
-             for (const auto& header : header_to_import) {
+             for (const auto& header : headers_to_import) {
                p->Emit({{"header", header}},
                        R"objc(
                          #import "$header$"
diff --git a/src/google/protobuf/compiler/objectivec/import_writer.h b/src/google/protobuf/compiler/objectivec/import_writer.h
index f1134e1..2fec890 100644
--- a/src/google/protobuf/compiler/objectivec/import_writer.h
+++ b/src/google/protobuf/compiler/objectivec/import_writer.h
@@ -50,15 +50,18 @@
   ImportWriter(const std::string& generate_for_named_framework,
                const std::string& named_framework_to_proto_path_mappings_path,
                const std::string& runtime_import_prefix,
-               bool include_wkt_imports);
+               bool for_bundled_proto);
   ~ImportWriter() = default;
 
   void AddFile(const FileDescriptor* file, const std::string& header_extension);
-  void Emit(io::Printer* p) const;
+  void AddRuntimeImport(const std::string& header_name);
+
+  void EmitFileImports(io::Printer* p) const;
+  void EmitRuntimeImports(io::Printer* p, bool default_cpp_symbol) const;
 
   static void EmitRuntimeImports(
-      io::Printer* p, const std::vector<std::string>& header_to_import,
-      const std::string& runtime_import_prefix,
+      io::Printer* p, const std::vector<std::string>& headers_to_import,
+      const std::string& runtime_import_prefix, bool is_bundled_proto = false,
       bool default_cpp_symbol = false);
 
  private:
@@ -67,8 +70,8 @@
   const std::string generate_for_named_framework_;
   const std::string named_framework_to_proto_path_mappings_path_;
   const std::string runtime_import_prefix_;
-  const bool include_wkt_imports_;
   absl::flat_hash_map<std::string, std::string> proto_file_to_framework_name_;
+  bool for_bundled_proto_;
   bool need_to_parse_mapping_file_;
 
   std::vector<std::string> protobuf_imports_;
diff --git a/src/google/protobuf/compiler/objectivec/map_field.cc b/src/google/protobuf/compiler/objectivec/map_field.cc
index 5285cde..a9c73fd 100644
--- a/src/google/protobuf/compiler/objectivec/map_field.cc
+++ b/src/google/protobuf/compiler/objectivec/map_field.cc
@@ -187,7 +187,7 @@
       descriptor_->file() == value_msg_descriptor->file()) {
     const std::string& value_storage_type =
         value_field_generator_->variable("storage_type");
-    fwd_decls->insert("@class " + value_storage_type);
+    fwd_decls->insert(absl::StrCat("@class ", value_storage_type, ";"));
   }
 }
 
diff --git a/src/google/protobuf/compiler/objectivec/message_field.cc b/src/google/protobuf/compiler/objectivec/message_field.cc
index c4e17d7..8da2f82 100644
--- a/src/google/protobuf/compiler/objectivec/message_field.cc
+++ b/src/google/protobuf/compiler/objectivec/message_field.cc
@@ -77,7 +77,7 @@
                                      descriptor_->message_type()->file())) ||
       descriptor_->file() == descriptor_->message_type()->file()) {
     // Class name is already in "storage_type".
-    fwd_decls->insert("@class " + variable("storage_type"));
+    fwd_decls->insert(absl::StrCat("@class ", variable("storage_type"), ";"));
   }
 }
 
@@ -107,7 +107,7 @@
                                      descriptor_->message_type()->file())) ||
       descriptor_->file() == descriptor_->message_type()->file()) {
     // Class name is already in "storage_type".
-    fwd_decls->insert("@class " + variable("storage_type"));
+    fwd_decls->insert(absl::StrCat("@class ", variable("storage_type"), ";"));
   }
 }