Skip to content

Commit

Permalink
Generate Kythe annotations for objc messages.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Jan 24, 2025
1 parent ca29ed1 commit 8868171
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 40 deletions.
21 changes: 13 additions & 8 deletions src/google/protobuf/compiler/objectivec/field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace compiler {
namespace objectivec {

namespace {
using Sub = ::google::protobuf::io::Printer::Sub;

void SetCommonFieldVariables(const FieldDescriptor* descriptor,
SubstitutionMap& variables) {
Expand All @@ -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",
Expand Down Expand Up @@ -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");
Expand All @@ -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");
}
Expand Down Expand Up @@ -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"))) {
Expand Down
15 changes: 14 additions & 1 deletion src/google/protobuf/compiler/objectivec/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> fwd_decls;
for (const auto& generator : message_generators_) {
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/google/protobuf/compiler/objectivec/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
39 changes: 37 additions & 2 deletions src/google/protobuf/compiler/objectivec/generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand All @@ -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<GeneratedCodeInfo> 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).
Expand Down
10 changes: 10 additions & 0 deletions src/google/protobuf/compiler/objectivec/helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>

#include "absl/log/absl_check.h"
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/google/protobuf/compiler/objectivec/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ class SubstitutionMap {
// All arguments are forwarded to `io::Printer::Sub`.
template <typename... Args>
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<io::Printer::Sub> subs_;
Expand Down
15 changes: 10 additions & 5 deletions src/google/protobuf/compiler/objectivec/message.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace compiler {
namespace objectivec {

namespace {
using Sub = ::google::protobuf::io::Printer::Sub;

struct FieldOrderingByNumber {
inline bool operator()(const FieldDescriptor* a,
Expand Down Expand Up @@ -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_,
Expand All @@ -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");
Expand Down
53 changes: 33 additions & 20 deletions src/google/protobuf/compiler/objectivec/oneof.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}}},
Expand All @@ -63,41 +68,49 @@ 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");
}

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];
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions src/google/protobuf/compiler/objectivec/oneof.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

#include <string>

#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"
Expand Down Expand Up @@ -46,7 +45,7 @@ class OneofGenerator {
private:
const OneofDescriptor* descriptor_;
const GenerationOptions& generation_options_;
absl::flat_hash_map<absl::string_view, std::string> variables_;
SubstitutionMap variables_;
};

} // namespace objectivec
Expand Down
9 changes: 9 additions & 0 deletions src/google/protobuf/compiler/objectivec/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8868171

Please sign in to comment.