From 88681710b5df5e4af9d3e06a505f501813c36207 Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Fri, 24 Jan 2025 05:51:04 -0800 Subject: [PATCH] Generate Kythe annotations for objc messages. This introduces both the basic plumbing for generating metadata (guarded by compiler flags), as well as the initial output of metadata for messages and fields. We largely follows the outline used in the C++ generator. The second part requires a bit of refactoring on the `FieldGenerator` class since it assumed it was only storing plain string substitutions. This does not implement cross-references for all symbols (e.g. enums and oneofs); these will be done in followups. PiperOrigin-RevId: 719269040 --- .../protobuf/compiler/objectivec/field.cc | 21 +++++--- .../protobuf/compiler/objectivec/file.cc | 15 +++++- .../protobuf/compiler/objectivec/file.h | 2 +- .../protobuf/compiler/objectivec/generator.cc | 39 +++++++++++++- .../protobuf/compiler/objectivec/helpers.cc | 10 ++++ .../protobuf/compiler/objectivec/helpers.h | 5 ++ .../protobuf/compiler/objectivec/message.cc | 15 ++++-- .../protobuf/compiler/objectivec/oneof.cc | 53 ++++++++++++------- .../protobuf/compiler/objectivec/oneof.h | 5 +- .../protobuf/compiler/objectivec/options.h | 9 ++++ 10 files changed, 134 insertions(+), 40 deletions(-) diff --git a/src/google/protobuf/compiler/objectivec/field.cc b/src/google/protobuf/compiler/objectivec/field.cc index c6c826c686717..697c199266d69 100644 --- a/src/google/protobuf/compiler/objectivec/field.cc +++ b/src/google/protobuf/compiler/objectivec/field.cc @@ -33,6 +33,7 @@ namespace compiler { namespace objectivec { namespace { +using Sub = ::google::protobuf::io::Printer::Sub; void SetCommonFieldVariables(const FieldDescriptor* descriptor, SubstitutionMap& variables) { @@ -49,14 +50,18 @@ void SetCommonFieldVariables(const FieldDescriptor* descriptor, const bool needs_custom_name = (raw_field_name != un_camel_case_name); const std::string& classname = ClassName(descriptor->containing_type()); - variables.Set("classname", classname); - variables.Set("name", camel_case_name); + variables.Set(Sub("classname", classname).AnnotatedAs(descriptor)); + variables.Set(Sub("name", camel_case_name).AnnotatedAs(descriptor)); const std::string& capitalized_name = FieldNameCapitalized(descriptor); - variables.Set("capitalized_name", capitalized_name); + variables.Set( + Sub("hazzer_name", "has" + capitalized_name).AnnotatedAs(descriptor)); + variables.Set( + Sub("capitalized_name", capitalized_name).AnnotatedAs(descriptor)); variables.Set("raw_field_name", raw_field_name); - variables.Set("field_number_name", - absl::StrCat(classname, "_FieldNumber_", capitalized_name)); + variables.Set(Sub("field_number_name", + absl::StrCat(classname, "_FieldNumber_", capitalized_name)) + .AnnotatedAs(descriptor)); variables.Set("field_number", absl::StrCat(descriptor->number())); variables.Set( "property_type", @@ -310,7 +315,7 @@ void SingleFieldGenerator::GeneratePropertyDeclaration( )objc"); if (WantsHasProperty()) { printer->Emit(R"objc( - @property(nonatomic, readwrite) BOOL has$capitalized_name$$ deprecated_attribute$; + @property(nonatomic, readwrite) BOOL $hazzer_name$$ deprecated_attribute$; )objc"); } printer->Emit("\n"); @@ -320,7 +325,7 @@ void SingleFieldGenerator::GeneratePropertyImplementation( io::Printer* printer) const { auto vars = variables_.Install(printer); if (WantsHasProperty()) { - printer->Emit("@dynamic has$capitalized_name$, $name$;\n"); + printer->Emit("@dynamic $hazzer_name$, $name$;\n"); } else { printer->Emit("@dynamic $name$;\n"); } @@ -369,7 +374,7 @@ void ObjCObjFieldGenerator::GeneratePropertyDeclaration( if (WantsHasProperty()) { printer->Emit(R"objc( /** Test to see if @c $name$ has been set. */ - @property(nonatomic, readwrite) BOOL has$capitalized_name$$ deprecated_attribute$; + @property(nonatomic, readwrite) BOOL $hazzer_name$$ deprecated_attribute$; )objc"); } if (IsInitName(variable("name"))) { diff --git a/src/google/protobuf/compiler/objectivec/file.cc b/src/google/protobuf/compiler/objectivec/file.cc index 6020d415507c3..f4032eac7f2ae 100644 --- a/src/google/protobuf/compiler/objectivec/file.cc +++ b/src/google/protobuf/compiler/objectivec/file.cc @@ -303,7 +303,8 @@ FileGenerator::FileGenerator(Edition edition, const FileDescriptor* file, } } -void FileGenerator::GenerateHeader(io::Printer* p) const { +void FileGenerator::GenerateHeader(io::Printer* p, + absl::string_view info_path) const { GenerateFile(p, GeneratedFileType::kHeader, [&] { absl::btree_set fwd_decls; for (const auto& generator : message_generators_) { @@ -321,6 +322,18 @@ void FileGenerator::GenerateHeader(io::Printer* p) const { p->Emit("NS_ASSUME_NONNULL_BEGIN\n\n"); + if (!info_path.empty()) { + p->Emit({{"info_path", info_path}, + {"guard", generation_options_.annotation_guard_name}, + {"pragma", generation_options_.annotation_pragma_name}}, + R"objc( + #ifdef $guard$ + #pragma $pragma$ "$info_path$" + #endif // $guard$ + )objc"); + p->Emit("\n"); + } + for (const auto& generator : enum_generators_) { generator->GenerateHeader(p); } diff --git a/src/google/protobuf/compiler/objectivec/file.h b/src/google/protobuf/compiler/objectivec/file.h index baef5a0a2d884..bbed4b94b8fd0 100644 --- a/src/google/protobuf/compiler/objectivec/file.h +++ b/src/google/protobuf/compiler/objectivec/file.h @@ -63,7 +63,7 @@ class FileGenerator { FileGenerator(const FileGenerator&) = delete; FileGenerator& operator=(const FileGenerator&) = delete; - void GenerateHeader(io::Printer* p) const; + void GenerateHeader(io::Printer* p, absl::string_view info_path) const; void GenerateSource(io::Printer* p) const; int NumEnums() const { return enum_generators_.size(); } diff --git a/src/google/protobuf/compiler/objectivec/generator.cc b/src/google/protobuf/compiler/objectivec/generator.cc index f55aea984eed4..ea4a7166cd740 100644 --- a/src/google/protobuf/compiler/objectivec/generator.cc +++ b/src/google/protobuf/compiler/objectivec/generator.cc @@ -294,6 +294,10 @@ bool ObjectiveCGenerator::GenerateAll( options[i].second); return false; } + } else if (options[i].first == "annotation_pragma_name") { + generation_options.annotation_pragma_name = options[i].second; + } else if (options[i].first == "annotation_guard_name") { + generation_options.annotation_guard_name = options[i].second; } else { *error = absl::StrCat("error: Unknown generator option: ", options[i].first); @@ -384,6 +388,19 @@ bool ObjectiveCGenerator::GenerateAll( // ----------------------------------------------------------------- + if (generation_options.annotation_guard_name.empty() != + generation_options.annotation_pragma_name.empty()) { + *error = + "error: both annotation_guard_name and annotation_pragma_name must " + "be set to output annotations"; + return false; + } + bool should_annotate_headers = + !generation_options.annotation_pragma_name.empty() && + !generation_options.annotation_guard_name.empty(); + + // ----------------------------------------------------------------- + // Validate the objc prefix/package pairings. if (!ValidateObjCClassPrefixes(files, validation_options, error)) { // *error will have been filled in. @@ -400,13 +417,31 @@ bool ObjectiveCGenerator::GenerateAll( { auto output = absl::WrapUnique(context->Open(absl::StrCat(filepath, ".pbobjc.h"))); - io::Printer printer(output.get()); - file_generator.GenerateHeader(&printer); + GeneratedCodeInfo annotations; + io::AnnotationProtoCollector annotation_collector( + &annotations); + io::Printer::Options options; + std::string info_path = ""; + if (should_annotate_headers) { + info_path = absl::StrCat(filepath, ".pbobjc.h.meta"); + options.annotation_collector = &annotation_collector; + } + io::Printer printer(output.get(), options); + file_generator.GenerateHeader(&printer, info_path); if (printer.failed()) { *error = absl::StrCat("error: internal error generating a header: ", file->name()); return false; } + + if (should_annotate_headers) { + auto info_output = absl::WrapUnique(context->Open(info_path)); + if (!annotations.SerializeToZeroCopyStream(info_output.get())) { + *error = absl::StrCat("error: internal error writing annotations: ", + info_path); + return false; + } + } } // Generate m file(s). diff --git a/src/google/protobuf/compiler/objectivec/helpers.cc b/src/google/protobuf/compiler/objectivec/helpers.cc index 6e380baa49bbb..ef9e71db5c03f 100644 --- a/src/google/protobuf/compiler/objectivec/helpers.cc +++ b/src/google/protobuf/compiler/objectivec/helpers.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "absl/log/absl_check.h" @@ -428,6 +429,15 @@ bool IsWKTWithObjCCategory(const Descriptor* descriptor) { return false; } +void SubstitutionMap::Set(io::Printer::Sub&& sub) { + if (auto [it, inserted] = subs_map_.try_emplace(sub.key(), subs_.size()); + !inserted) { + subs_[it->second] = std::move(sub); + } else { + subs_.emplace_back(std::move(sub)); + } +} + } // namespace objectivec } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/objectivec/helpers.h b/src/google/protobuf/compiler/objectivec/helpers.h index 6d23542b9ee87..3c65b83ab3f9b 100644 --- a/src/google/protobuf/compiler/objectivec/helpers.h +++ b/src/google/protobuf/compiler/objectivec/helpers.h @@ -185,6 +185,11 @@ class SubstitutionMap { // All arguments are forwarded to `io::Printer::Sub`. template void Set(std::string key, Args&&... args); + // Same as above, but takes a `io::Printer::Sub` directly. + // + // This is necessary to use advanced features of `io::Printer::Sub` like + // annotations. + void Set(io::Printer::Sub&& sub); private: std::vector subs_; diff --git a/src/google/protobuf/compiler/objectivec/message.cc b/src/google/protobuf/compiler/objectivec/message.cc index d41fd1483791c..4cb0f0563fc2b 100644 --- a/src/google/protobuf/compiler/objectivec/message.cc +++ b/src/google/protobuf/compiler/objectivec/message.cc @@ -37,6 +37,7 @@ namespace compiler { namespace objectivec { namespace { +using Sub = ::google::protobuf::io::Printer::Sub; struct FieldOrderingByNumber { inline bool operator()(const FieldDescriptor* a, @@ -290,10 +291,10 @@ void MessageGenerator::DetermineObjectiveCClassDefinitions( } void MessageGenerator::GenerateMessageHeader(io::Printer* printer) const { - auto vars = printer->WithVars({{"classname", class_name_}}); + auto vars = printer->WithVars( + {Sub("classname", class_name_).AnnotatedAs(descriptor_)}); printer->Emit( - {io::Printer::Sub("deprecated_attribute", deprecated_attribute_) - .WithSuffix(";"), + {Sub("deprecated_attribute", deprecated_attribute_).WithSuffix(";"), {"message_comments", [&] { EmitCommentsString(printer, generation_options_, descriptor_, @@ -302,8 +303,12 @@ void MessageGenerator::GenerateMessageHeader(io::Printer* printer) const { {"message_fieldnum_enum", [&] { if (descriptor_->field_count() == 0) return; - printer->Emit(R"objc( - typedef GPB_ENUM($classname$_FieldNumber) { + printer->Emit({Sub("field_number_enum_name", + absl::StrCat(printer->LookupVar("classname"), + "_FieldNumber")) + .AnnotatedAs(descriptor_)}, + R"objc( + typedef GPB_ENUM($field_number_enum_name$) { $message_fieldnum_enum_values$, }; )objc"); diff --git a/src/google/protobuf/compiler/objectivec/oneof.cc b/src/google/protobuf/compiler/objectivec/oneof.cc index 3cd722491f2e8..a3be55e06ac2a 100644 --- a/src/google/protobuf/compiler/objectivec/oneof.cc +++ b/src/google/protobuf/compiler/objectivec/oneof.cc @@ -21,34 +21,39 @@ namespace protobuf { namespace compiler { namespace objectivec { +using Sub = ::google::protobuf::io::Printer::Sub; + OneofGenerator::OneofGenerator(const OneofDescriptor* descriptor, const GenerationOptions& generation_options) : descriptor_(descriptor), generation_options_(generation_options) { - variables_["enum_name"] = OneofEnumName(descriptor_); - variables_["name"] = OneofName(descriptor_); - variables_["capitalized_name"] = OneofNameCapitalized(descriptor_); - variables_["raw_index"] = absl::StrCat(descriptor_->index()); + variables_.Set("enum_name", OneofEnumName(descriptor_)); + variables_.Set("name", OneofName(descriptor_)); + variables_.Set("capitalized_name", OneofNameCapitalized(descriptor_)); + variables_.Set("raw_index", absl::StrCat(descriptor_->index())); const Descriptor* msg_descriptor = descriptor_->containing_type(); - variables_["owning_message_class"] = ClassName(msg_descriptor); + variables_.Set("owning_message_class", ClassName(msg_descriptor)); } void OneofGenerator::SetOneofIndexBase(int index_base) { int index = descriptor_->index() + index_base; // Flip the sign to mark it as a oneof. - variables_["index"] = absl::StrCat(-index); + variables_.Set("index", absl::StrCat(-index)); } void OneofGenerator::GenerateCaseEnum(io::Printer* printer) const { - auto vars = printer->WithVars(variables_); + auto vars = variables_.Install(printer); printer->Emit({{"cases", [&] { for (int j = 0; j < descriptor_->field_count(); j++) { const FieldDescriptor* field = descriptor_->field(j); printer->Emit( - {{"field_name", FieldNameCapitalized(field)}, - {"field_number", field->number()}}, + {Sub("enum_entry_name", + absl::StrCat(printer->LookupVar("enum_name"), + "_", FieldNameCapitalized(field))) + .AnnotatedAs(field), + Sub("field_number", field->number())}, R"objc( - $enum_name$_$field_name$ = $field_number$, + $enum_entry_name$ = $field_number$, )objc"); } }}}, @@ -63,33 +68,41 @@ void OneofGenerator::GenerateCaseEnum(io::Printer* printer) const { void OneofGenerator::GeneratePublicCasePropertyDeclaration( io::Printer* printer) const { - auto vars = printer->WithVars(variables_); - printer->Emit({{"comments", + auto vars = variables_.Install(printer); + printer->Emit({Sub("oneof_getter_name", + absl::StrCat(printer->LookupVar("name"), "OneOfCase")) + .AnnotatedAs(descriptor_), + {"comments", [&] { EmitCommentsString(printer, generation_options_, descriptor_); }}}, R"objc( $comments$; - @property(nonatomic, readonly) $enum_name$ $name$OneOfCase; + @property(nonatomic, readonly) $enum_name$ $oneof_getter_name$; )objc"); printer->Emit("\n"); } void OneofGenerator::GenerateClearFunctionDeclaration( io::Printer* printer) const { - auto vars = printer->WithVars(variables_); - printer->Emit(R"objc( + auto vars = variables_.Install(printer); + printer->Emit( + {Sub("clear_function_name", + absl::StrCat(printer->LookupVar("owning_message_class"), "_Clear", + printer->LookupVar("capitalized_name"), "OneOfCase")) + .AnnotatedAs(descriptor_)}, + R"objc( /** * Clears whatever value was set for the oneof '$name$'. **/ - void $owning_message_class$_Clear$capitalized_name$OneOfCase($owning_message_class$ *message); + void $clear_function_name$($owning_message_class$ *message); )objc"); } void OneofGenerator::GeneratePropertyImplementation( io::Printer* printer) const { - auto vars = printer->WithVars(variables_); + auto vars = variables_.Install(printer); printer->Emit(R"objc( @dynamic $name$OneOfCase; )objc"); @@ -97,7 +110,7 @@ void OneofGenerator::GeneratePropertyImplementation( void OneofGenerator::GenerateClearFunctionImplementation( io::Printer* printer) const { - auto vars = printer->WithVars(variables_); + auto vars = variables_.Install(printer); printer->Emit(R"objc( void $owning_message_class$_Clear$capitalized_name$OneOfCase($owning_message_class$ *message) { GPBDescriptor *descriptor = [$owning_message_class$ descriptor]; @@ -108,11 +121,11 @@ void OneofGenerator::GenerateClearFunctionImplementation( } std::string OneofGenerator::DescriptorName() const { - return variables_.find("name")->second; + return variables_.Value("name"); } std::string OneofGenerator::HasIndexAsString() const { - return variables_.find("index")->second; + return variables_.Value("index"); } } // namespace objectivec diff --git a/src/google/protobuf/compiler/objectivec/oneof.h b/src/google/protobuf/compiler/objectivec/oneof.h index c849e885127a1..abe9a6121203c 100644 --- a/src/google/protobuf/compiler/objectivec/oneof.h +++ b/src/google/protobuf/compiler/objectivec/oneof.h @@ -10,8 +10,7 @@ #include -#include "absl/container/flat_hash_map.h" -#include "absl/strings/string_view.h" +#include "google/protobuf/compiler/objectivec/helpers.h" #include "google/protobuf/compiler/objectivec/options.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/io/printer.h" @@ -46,7 +45,7 @@ class OneofGenerator { private: const OneofDescriptor* descriptor_; const GenerationOptions& generation_options_; - absl::flat_hash_map variables_; + SubstitutionMap variables_; }; } // namespace objectivec diff --git a/src/google/protobuf/compiler/objectivec/options.h b/src/google/protobuf/compiler/objectivec/options.h index 8c80ae4aa4ba2..8ac947674c468 100644 --- a/src/google/protobuf/compiler/objectivec/options.h +++ b/src/google/protobuf/compiler/objectivec/options.h @@ -29,6 +29,15 @@ struct GenerationOptions { // in behavior or go away at any time. bool experimental_multi_source_generation = false; bool experimental_strip_nonfunctional_codegen = false; + + // The name of the pragma that will be used to indicate the start of the + // metadata annotations. Must be set (along with `annotation_guard_name`) for + // cross-references to be generated. + std::string annotation_pragma_name; + // The name of the preprocessor guard that will be used to guard the metadata + // annotations. Must be set (along with `annotation_pragma_name`) for + // cross-references to be generated. + std::string annotation_guard_name; }; } // namespace objectivec