2024-03-15 12:31:34 +08:00

285 lines
11 KiB
C++

// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "google/protobuf/compiler/objectivec/enum.h"
#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/text_format_decode_data.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)
: descriptor_(descriptor), 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 in a proto3 syntax file where 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 now 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` attribute, as the
// default will be what is needed.
printer->Emit(
{
{"enum_name", name_},
{"enum_comments", [&] { EmitCommentsString(printer, 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 = CommentStringFlags::kNone;
for (const auto* v : all_values_) {
if (alias_values_to_skip_.contains(v)) continue;
printer->Emit(
{
{"name", EnumValueName(v)},
{"comments",
[&] { EmitCommentsString(printer, v, comment_flags); }},
{"deprecated_attribute",
GetOptionalDeprecatedAttribute(v)},
{"value", SafelyPrintIntToCode(v->number())},
},
R"objc(
$comments$
$name$$deprecated_attribute$ = $value$,
)objc");
comment_flags = CommentStringFlags::kAddLeadingNewline;
}
}},
},
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