blob: c8bfcbc3dfe74b8ce71404ddaf7d8421088c84c9 [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/compiler/objectivec/enum.h"
#include <cstddef>
#include <limits>
#include <string>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/compiler/objectivec/helpers.h"
#include "google/protobuf/compiler/objectivec/names.h"
#include "google/protobuf/compiler/objectivec/options.h"
#include "google/protobuf/compiler/objectivec/tf_decode_data.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/io/printer.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace objectivec {
namespace {
std::string SafelyPrintIntToCode(int v) {
if (v == std::numeric_limits<int>::min()) {
// Some compilers try to parse -2147483648 as two tokens and then get spicy
// about the fact that +2147483648 cannot be represented as an int.
return absl::StrCat(v + 1, " - 1");
} else {
return absl::StrCat(v);
}
}
} // namespace
EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor,
const GenerationOptions& generation_options)
: descriptor_(descriptor),
generation_options_(generation_options),
name_(EnumName(descriptor_)) {
// Track the names for the enum values, and if an alias overlaps a base
// value, skip making a name for it. Likewise if two alias overlap, the
// first one wins.
// The one gap in this logic is if two base values overlap, but for that
// to happen you have to have "Foo" and "FOO" or "FOO_BAR" and "FooBar",
// and if an enum has that, it is already going to be confusing and a
// compile error is just fine.
// The values are still tracked to support the reflection apis and
// TextFormat handing since they are different there.
absl::flat_hash_set<std::string> value_names;
for (int i = 0; i < descriptor_->value_count(); i++) {
const EnumValueDescriptor* value = descriptor_->value(i);
const EnumValueDescriptor* canonical_value =
descriptor_->FindValueByNumber(value->number());
if (value == canonical_value) {
base_values_.push_back(value);
value_names.insert(EnumValueName(value));
} else {
if (!value_names.insert(EnumValueName(value)).second) {
alias_values_to_skip_.insert(value);
}
}
all_values_.push_back(value);
}
}
void EnumGenerator::GenerateHeader(io::Printer* printer) const {
// Swift 5 included SE0192 "Handling Future Enum Cases"
// https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
// Since a .proto file can get new values added to an enum at any time, they
// are effectively "non-frozen". Even with an EnumType::Open there is support
// for the unknown value, an edit to the file can always add a new value
// moving something from unknown to known. Since Swift is ABI stable, it also
// means a binary could contain Swift compiled against one version of the
// .pbobjc.h file, but finally linked against an enum with more cases. So the
// Swift code will always have to treat ObjC Proto Enums as "non-frozen". The
// default behavior in SE0192 is for all objc enums to be "non-frozen" unless
// marked as otherwise, so this means this generation doesn't have to bother
// with the `enum_extensibility` clang attribute, as the default will be what
// is needed.
printer->Emit(
{
{"enum_name", name_},
{"enum_comments",
[&] {
EmitCommentsString(printer, generation_options_, descriptor_);
}},
{"enum_deprecated_attribute",
GetOptionalDeprecatedAttribute(descriptor_, descriptor_->file())},
{"maybe_unknown_value",
[&] {
if (descriptor_->is_closed()) return;
// Include the unknown value.
printer->Emit(R"objc(
/**
* Value used if any message's field encounters a value that is not defined
* by this enum. The message will also have C functions to get/set the rawValue
* of the field.
**/
$enum_name$_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
)objc");
}},
{"enum_values",
[&] {
CommentStringFlags comment_flags = kCommentStringFlags_None;
for (const auto* v : all_values_) {
if (alias_values_to_skip_.contains(v)) continue;
printer->Emit(
{
{"name", EnumValueName(v)},
{"comments",
[&] {
EmitCommentsString(printer, generation_options_, v,
comment_flags);
}},
{"deprecated_attribute",
GetOptionalDeprecatedAttribute(v)},
{"value", SafelyPrintIntToCode(v->number())},
},
R"objc(
$comments$
$name$$ deprecated_attribute$ = $value$,
)objc");
comment_flags = kCommentStringFlags_AddLeadingNewline;
}
}},
},
R"objc(
#pragma mark - Enum $enum_name$
$enum_comments$
typedef$ enum_deprecated_attribute$ GPB_ENUM($enum_name$) {
$maybe_unknown_value$
$enum_values$
};
GPBEnumDescriptor *$enum_name$_EnumDescriptor(void);
/**
* Checks to see if the given value is defined by the enum or was not known at
* the time this source was generated.
**/
BOOL $enum_name$_IsValidValue(int32_t value);
)objc");
printer->Emit("\n");
}
void EnumGenerator::GenerateSource(io::Printer* printer) const {
// Note: For the TextFormat decode info, we can't use the enum value as
// the key because protocol buffer enums have 'allow_alias', which lets
// a value be used more than once. Instead, the index into the list of
// enum value descriptions is used. Note: start with -1 so the first one
// will be zero.
TextFormatDecodeData text_format_decode_data;
int enum_value_description_key = -1;
std::string text_blob;
for (const auto* v : all_values_) {
++enum_value_description_key;
std::string short_name(EnumValueShortName(v));
text_blob += short_name + '\0';
if (UnCamelCaseEnumShortName(short_name) != v->name()) {
text_format_decode_data.AddString(enum_value_description_key, short_name,
v->name());
}
}
printer->Emit(
{{"name", name_},
{"values_name_blob",
[&] {
static const int kBytesPerLine = 40; // allow for escaping
for (size_t i = 0; i < text_blob.size(); i += kBytesPerLine) {
printer->Emit({{"data", EscapeTrigraphs(absl::CEscape(
text_blob.substr(i, kBytesPerLine)))},
{"ending_semi",
(i + kBytesPerLine) < text_blob.size() ? "" : ";"}},
R"objc(
"$data$"$ending_semi$
)objc");
}
}},
{"values",
[&] {
for (const auto* v : all_values_) {
printer->Emit({{"value_name", EnumValueName(v)}},
R"objc(
$value_name$,
)objc");
}
}},
{"maybe_extra_text_format_decl",
[&] {
if (text_format_decode_data.num_entries()) {
printer->Emit({{"extraTextFormatInfo",
absl::CEscape(text_format_decode_data.Data())}},
R"objc(
static const char *extraTextFormatInfo = "$extraTextFormatInfo$";
)objc");
}
}},
{"maybe_extraTextFormatInfo",
// Could not find a better way to get this extra line inserted and
// correctly formatted.
(text_format_decode_data.num_entries() == 0
? ""
: "\n "
"extraTextFormatInfo:extraTextFormatInfo")},
{"enum_flags", descriptor_->is_closed()
? "GPBEnumDescriptorInitializationFlag_IsClosed"
: "GPBEnumDescriptorInitializationFlag_None"},
{"enum_cases",
[&] {
for (const auto* v : base_values_) {
printer->Emit({{"case_name", EnumValueName(v)}},
R"objc(
case $case_name$:
)objc");
}
}}},
R"objc(
#pragma mark - Enum $name$
GPBEnumDescriptor *$name$_EnumDescriptor(void) {
static _Atomic(GPBEnumDescriptor*) descriptor = nil;
if (!descriptor) {
GPB_DEBUG_CHECK_RUNTIME_VERSIONS();
static const char *valueNames =
$values_name_blob$
static const int32_t values[] = {
$values$
};
$maybe_extra_text_format_decl$
GPBEnumDescriptor *worker =
[GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol($name$)
valueNames:valueNames
values:values
count:(uint32_t)(sizeof(values) / sizeof(int32_t))
enumVerifier:$name$_IsValidValue
flags:$enum_flags$$maybe_extraTextFormatInfo$];
GPBEnumDescriptor *expected = nil;
if (!atomic_compare_exchange_strong(&descriptor, &expected, worker)) {
[worker release];
}
}
return descriptor;
}
BOOL $name$_IsValidValue(int32_t value__) {
switch (value__) {
$enum_cases$
return YES;
default:
return NO;
}
}
)objc");
printer->Emit("\n");
}
} // namespace objectivec
} // namespace compiler
} // namespace protobuf
} // namespace google